重构
This commit is contained in:
parent
6bc38a94be
commit
ea2a5dd645
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.iml
|
||||
.idea/
|
||||
target/
|
||||
src/main/resources/json/
|
42
README.md
42
README.md
@ -1,4 +1,41 @@
|
||||
# lnglat2Geo
|
||||
# ChinaGeoLib
|
||||
修改自: https://github.com/deng0515001/lnglat2Geo, 非常感谢原作者的贡献,我完全照搬了原作者的算法实现,但是我不能理解这些算法的原理。
|
||||
|
||||
修改点如下:
|
||||
|
||||
1. 使用java完全重写Scala代码,原因如下
|
||||
1. 作为lib,java和scala依赖是单项的,Scala程序员可以看懂java,但是java程序员不一定能看懂Scala
|
||||
2. 作为lib库,需要考虑库的精简,他引入了Scala的runtime,将会带来额外的jar包依赖
|
||||
3. GeoTrans的标准API并不是可以无缝提供给java端使用(部分API java无法调用,因为有很多scala的特性)
|
||||
4. scala语法糖很舒服,但是进行代码cr能发现很多带有性能风险的用法,以及Scala函数式上代码结构不清晰(简写大括号、同函数if-else分支深度过多等)
|
||||
2. 重新设计数据的序列化和反序列化
|
||||
1. 提供json和bin两种格式数据接入,并提供两种格式的转换桥梁。开发状态可以使用json,生产使用bin
|
||||
2. 删除java的Serializable进行数据序列化的方法,工程实践上他从来不是稳定可靠的方式
|
||||
1. 当遇到字节码降级的时候,他会反序列化失败(如android 语法脱糖过程)
|
||||
2. 可能由于jvm实现具体细节导致不稳定
|
||||
3. 效率和性能并不是最优
|
||||
4. lib库混淆时,他会反序列化失败
|
||||
3. 使用Leb128编码:这在原作者的二进制数据格式上带来了5个百分点的优化(原作者:40.46%,现方案:35.67%)
|
||||
4. 二进制反序列化提速:数据初始化只需要6.5秒(原作者:45秒)
|
||||
1. 分别耗时在数据加载到内存,反序列化, 我们手写序列化和反序列化精确控制数据格式和组织方式(原始数据加载大约500毫秒即可完成,文件大小61M)
|
||||
2. 数据结构计算:我把计算结果缓存到本地文件系统。数据计算时间:25s降低到5s,缓存文件大小:178M
|
||||
|
||||
## 离线数据编辑和修改
|
||||
```
|
||||
// 把数据dump到json中,根据自己的需要编辑这些文件
|
||||
new JSONLoader().dump(new File("src/main/resources/json/"), geoData);
|
||||
|
||||
//编辑文件完成后,在程序运行前,使用jsonloader替换binloader,用于加载json格式的离线数据
|
||||
ILoader.Storage.setLoader(new JSONLoader());
|
||||
|
||||
// 代码测试没问题,使用BinLoader再把数据dump为二进制格式
|
||||
new BinLoader().dump(new File("src/main/resources/data.bin"), geoData);
|
||||
|
||||
//线上,取消JSONLoader
|
||||
```
|
||||
|
||||
|
||||
## 原作者readme
|
||||
|
||||
经纬度转省市区县乡镇,速度快 -- 单线程50000次/秒;精度高 -- 准确率99.9999%
|
||||
|
||||
@ -12,8 +49,7 @@
|
||||
|
||||
4:获取所有行政区划关系数据等
|
||||
|
||||
使用方法:
|
||||
import com.dengxq.lnglat2Geo.GeoTrans 里面的所有方法均为公有接口
|
||||
使用方法: import com.dengxq.lnglat2Geo.GeoTrans 里面的所有方法均为公有接口
|
||||
|
||||
接口文档,参考博客: https://blog.csdn.net/deng0515001/article/details/99606156
|
||||
|
||||
|
111
pom.xml
111
pom.xml
@ -4,11 +4,6 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<properties>
|
||||
<scala.version>2.11.12</scala.version>
|
||||
<scala.tools.version>2.11</scala.tools.version>
|
||||
<scala.version.major>2.11</scala.version.major>
|
||||
<org.json4s.version>3.2.11</org.json4s.version>
|
||||
<org.json4s.native.version>3.2.11</org.json4s.native.version>
|
||||
<slf4j.log4j12.version>1.6.2</slf4j.log4j12.version>
|
||||
</properties>
|
||||
<groupId>com.github.deng0515001</groupId>
|
||||
@ -25,45 +20,35 @@
|
||||
<artifactId>s2-geometry-library-java</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 移植scala代码,tuple暂时用它来替代 -->
|
||||
<dependency>
|
||||
<groupId>com.speedment.common</groupId>
|
||||
<artifactId>tuple</artifactId>
|
||||
<version>3.2.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.9</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.scala-lang</groupId>
|
||||
<artifactId>scala-library</artifactId>
|
||||
<version>${scala.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.scala-lang</groupId>
|
||||
<artifactId>scala-compiler</artifactId>
|
||||
<version>${scala.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.scala-lang</groupId>
|
||||
<artifactId>scala-reflect</artifactId>
|
||||
<version>${scala.version}</version>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.22</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.json4s</groupId>
|
||||
<artifactId>json4s-jackson_${scala.version.major}</artifactId>
|
||||
<version>${org.json4s.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.79</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.scalatest</groupId>
|
||||
<artifactId>scalatest_${scala.version.major}</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<scope>test</scope>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@ -101,36 +86,13 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.scala-tools</groupId>
|
||||
<artifactId>maven-scala-plugin</artifactId>
|
||||
<version>2.15.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>scala-compile-first</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
|
||||
<configuration>
|
||||
<scalaVersion>${scala.version}</scalaVersion>
|
||||
<args>
|
||||
<arg>-target:jvm-1.7</arg>
|
||||
</args>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@ -181,7 +143,7 @@
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<!--<configuration>-->
|
||||
<!--<aggregate>true</aggregate>-->
|
||||
<!--<aggregate>true</aggregate>-->
|
||||
<!--</configuration>-->
|
||||
<executions>
|
||||
<execution>
|
||||
@ -195,35 +157,6 @@
|
||||
|
||||
</plugins>
|
||||
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
<!-- Environment Settings -->
|
||||
<repositories>
|
||||
<!-- local -->
|
||||
<repository>
|
||||
<id>my-local-repo</id>
|
||||
<url>file://${project.basedir}/lib</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>boundless</id>
|
||||
<name>Boundless Maven Repository</name>
|
||||
<url>http://repo.boundlessgeo.com/main</url>
|
||||
</repository>
|
||||
|
||||
<!-- for scalding -->
|
||||
<repository>
|
||||
<id>conjars.org</id>
|
||||
<url>http://conjars.org/repo</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
149
src/main/java/com/dengxq/lnglat2Geo/GeoTrans.java
Normal file
149
src/main/java/com/dengxq/lnglat2Geo/GeoTrans.java
Normal file
@ -0,0 +1,149 @@
|
||||
package com.dengxq.lnglat2Geo;
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.*;
|
||||
import com.dengxq.lnglat2Geo.entity.enums.CoordinateSystem;
|
||||
import com.dengxq.lnglat2Geo.entity.enums.DistrictLevel;
|
||||
import com.dengxq.lnglat2Geo.utils.GeoUtils;
|
||||
import com.speedment.common.tuple.Tuple2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GeoTrans {
|
||||
|
||||
|
||||
/**
|
||||
* 判断经纬度的行政区划
|
||||
*
|
||||
* @param lon 经度
|
||||
* @param lat 纬度
|
||||
* @param needStreet 是否需要街道信息
|
||||
* @param coordSys 输入经纬度的坐标系
|
||||
* @return 行政区划
|
||||
*/
|
||||
public static Admin determineAdmin(Double lon, Double lat, CoordinateSystem coordSys, Boolean needStreet) {
|
||||
if (needStreet == null) {
|
||||
needStreet = true;
|
||||
}
|
||||
return GeoTransImpl.determineAdmin(lon, lat, needStreet, coordSys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给出附近的所有商圈信息
|
||||
*
|
||||
* @param lon 经度
|
||||
* @param lat 纬度
|
||||
* @param radius 需要商圈的半径
|
||||
* @param coordSys 输入经纬度的坐标系 CoordinateSystem
|
||||
* @param needStreet 是否需要返回行政区划的街道信息
|
||||
* @return
|
||||
*/
|
||||
public static BusinessAreaInfo aroundBusinessAreas(Double lon, Double lat, Integer radius, CoordinateSystem coordSys, Boolean needStreet) {
|
||||
if (radius == null) {
|
||||
radius = 4000;
|
||||
}
|
||||
if (needStreet == null) {
|
||||
needStreet = true;
|
||||
}
|
||||
Tuple2<Double, Double> gcj02LonLat = GeoUtils.toGCJ02(lon, lat, coordSys);
|
||||
Admin admin = determineAdmin(gcj02LonLat.get0(), gcj02LonLat.get1(), CoordinateSystem.GCJ02, needStreet);
|
||||
//return GeoTransImpl.determineAreaByAdmin(gcj02LonLat._1, gcj02LonLat._2, admin, radius);
|
||||
return new BusinessAreaInfo(admin, GeoTransImpl.determineAreaByCityId(lon, lat, admin.cityCode, radius, CoordinateSystem.GCJ02));
|
||||
}
|
||||
|
||||
/**
|
||||
* 给出附近的所有商圈信息
|
||||
*
|
||||
* @param lon 经度
|
||||
* @param lat 纬度
|
||||
* @param radius 需要商圈的半径
|
||||
* @param coordSys 输入经纬度的坐标系 CoordinateSystem
|
||||
* @param cityID 输入城市adcode
|
||||
* @return
|
||||
*/
|
||||
public static List<BusinessArea> aroundBusinessAreasByCityID(
|
||||
Double lon, Double lat, Integer radius, CoordinateSystem coordSys, Integer cityID) {
|
||||
if (radius == null) {
|
||||
radius = 4000;
|
||||
}
|
||||
return GeoTransImpl.determineAreaByCityId(lon, lat, cityID, radius, coordSys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取城市级别
|
||||
*
|
||||
* @param adcodeOrName 城市adcode或者城市名
|
||||
* @return 城市级别
|
||||
*/
|
||||
public static String getCityLevel(String adcodeOrName) {
|
||||
return GeoTransImpl.getCityLevel(adcodeOrName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据地区code返回规范数据
|
||||
*
|
||||
* @param adcode 地区code
|
||||
* @return
|
||||
*/
|
||||
public static AdminNode normalizeName(int adcode) {
|
||||
return GeoTransImpl.normalizeName(adcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据地区name返回规范化的地区信息
|
||||
*
|
||||
* @return 规范化的地区信息
|
||||
*/
|
||||
public static java.util.List<AdminNode> normalizeName(String name, DistrictLevel level, Boolean isFullMatch) {
|
||||
return GeoTransImpl.normalizeName(name, level, isFullMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据所有信息返回规范化的地区信息
|
||||
*
|
||||
* @param province 省名 可为空
|
||||
* @param city 城市名 可为空
|
||||
* @param district 区县名 可为空
|
||||
* @param street 街道名 可为空
|
||||
* @param isFullMatch 所有输入区域是简称还是全名
|
||||
* @return 规范化的地区信息,可能有多个或不存在
|
||||
*/
|
||||
public static java.util.List<Admin> normalizeName(String province, String city, String district,
|
||||
String street, Boolean isFullMatch) {
|
||||
province = trimToEmpty(province);
|
||||
city = trimToEmpty(city);
|
||||
district = trimToEmpty(district);
|
||||
street = trimToEmpty(street);
|
||||
if (isFullMatch == null) {
|
||||
isFullMatch = false;
|
||||
}
|
||||
return GeoTransImpl.normalizeName(province, city, district, street, isFullMatch);
|
||||
}
|
||||
|
||||
private static String trimToEmpty(String input) {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有行政区划数据
|
||||
*
|
||||
* @return 所有行政区划数据,不包含街道
|
||||
*/
|
||||
public static Map<Integer, AdminNode> adminData() {
|
||||
return GeoTransImpl.adminData;
|
||||
}
|
||||
|
||||
public static Map<String, String> countryCode() {
|
||||
return GeoTransImpl.countryCode;
|
||||
}
|
||||
|
||||
// public static districtBoundary():Map[Int,List[List[Long]]]=
|
||||
//
|
||||
// {
|
||||
// AdminDataProvider.AdminLoader.loadBoundarySrc.map(s = > (s.code, s.boundary))
|
||||
// .toMap
|
||||
// }
|
||||
}
|
428
src/main/java/com/dengxq/lnglat2Geo/GeoTransImpl.java
Normal file
428
src/main/java/com/dengxq/lnglat2Geo/GeoTransImpl.java
Normal file
@ -0,0 +1,428 @@
|
||||
package com.dengxq.lnglat2Geo;
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.*;
|
||||
import com.dengxq.lnglat2Geo.entity.enums.CoordinateSystem;
|
||||
import com.dengxq.lnglat2Geo.entity.enums.DistrictLevel;
|
||||
import com.dengxq.lnglat2Geo.loader.ILoader;
|
||||
import com.dengxq.lnglat2Geo.utils.GeoUtils;
|
||||
import com.dengxq.lnglat2Geo.utils.LineUtils;
|
||||
import com.dengxq.lnglat2Geo.utils.S2Utils;
|
||||
import com.google.common.geometry.S2CellId;
|
||||
import com.google.common.geometry.S2LatLng;
|
||||
import com.speedment.common.tuple.Tuple2;
|
||||
import com.speedment.common.tuple.Tuple3;
|
||||
import com.speedment.common.tuple.Tuple4;
|
||||
import com.speedment.common.tuple.Tuples;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class GeoTransImpl {
|
||||
public static final int min_level = 12;
|
||||
public static Map<Integer, List<BusinessAreaData>> cityBusinessArea = ILoader.Storage.getOrLoad().getAreaGroups()
|
||||
.stream().collect(Collectors.toMap(s -> s.cityAdCode, s -> s.areas));
|
||||
|
||||
public static Map<Long, Integer> boundaryAdminCell = ILoader.Storage.getOrLoad().getOrCreateRuntimeBoundaryAdminCell();
|
||||
|
||||
public static Map<Long, List<Tuple3<Long, Integer, Integer>>> boundaryData =
|
||||
ILoader.Storage.getOrLoad().getOrCreateRuntimeBoundaryData();
|
||||
|
||||
public static Map<Long, List<Long>> boundaryIndex = ILoader.Storage.getOrLoad().getOrCreateRuntimeBoundaryIndex();
|
||||
public static Map<String, String> cityLevelData = ILoader.Storage.getOrLoad().getCityLevel();
|
||||
|
||||
public static Map<Integer, AdminNode> adminData = ILoader.Storage.getOrLoad().getAdminData()
|
||||
.stream().collect(
|
||||
Collectors.toMap(adminNode -> adminNode.id, adminNode -> adminNode)
|
||||
);
|
||||
|
||||
public static Map<Integer, AdminNode> streetData = ILoader.Storage.getOrLoad().getStreetData().stream().collect(
|
||||
Collectors.toMap(adminNode -> adminNode.id, adminNode -> adminNode)
|
||||
);
|
||||
|
||||
public static Map<String, String> countryCode = ILoader.Storage.getOrLoad().getCountryCode();
|
||||
|
||||
|
||||
public static Map<String, String> cityNameMap = new HashMap<String, String>() {{
|
||||
put("重庆市", "");
|
||||
put("上海市", "上海城区");
|
||||
put("北京市", "北京城区");
|
||||
put("天津市", "天津城区");
|
||||
put("那曲市", "那曲地区");
|
||||
}};
|
||||
|
||||
public static Map<String, String> districtNameMap = new HashMap<String, String>() {
|
||||
{
|
||||
put("云州区", "大同县");
|
||||
put("平城区", "城区");
|
||||
put("云冈区", "南郊区");
|
||||
put("余江区", "余江县");
|
||||
put("马龙区", "马龙县");
|
||||
put("光明区", "宝安区");
|
||||
put("怀仁区", "怀仁县");
|
||||
put("彬州市", "彬县");
|
||||
put("海安市", "海安县");
|
||||
put("漠河市", "漠河县");
|
||||
put("京山市", "京山县");
|
||||
put("济阳区", "济阳县");
|
||||
put("潞州区", "城区");
|
||||
put("上党区", "长治县");
|
||||
put("屯留区", "屯留县");
|
||||
put("潞城区", "潞城市");
|
||||
put("滦州市", "滦县");
|
||||
put("潜山市", "潜山县");
|
||||
put("邹平市", "邹平县");
|
||||
put("荔浦市", "荔浦县");
|
||||
put("兴仁市", "兴仁县");
|
||||
put("水富市", "水富县");
|
||||
put("华亭市", "华亭县");
|
||||
put("积石山县", "积石山保安族东乡族撒拉族自治县");
|
||||
put("元江县", "元江哈尼族彝族傣族自治县");
|
||||
put("双江县", "双江拉祜族佤族布朗族傣族自治县");
|
||||
put("孟连县", "孟连傣族拉祜族佤族自治县");
|
||||
put("镇沅县", "镇沅彝族哈尼族拉祜族自治县");
|
||||
put("大柴旦行政委员会", "海西蒙古族藏族自治州直辖");
|
||||
put("冷湖行政委员会", "海西蒙古族藏族自治州直辖");
|
||||
put("茫崖行政委员会", "海西蒙古族藏族自治州直辖");
|
||||
put("上饶县", "广信区");
|
||||
put("达孜区", "达孜县");
|
||||
put("色尼区", "那曲县");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static List<BusinessArea> determineAreaByCityId(Double lon, Double lat, int cityId, int radius, CoordinateSystem coordSys) {
|
||||
if (cityId == -1) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Tuple2<Double, Double> tuple2 = GeoUtils.toGCJ02(lon, lat, coordSys);
|
||||
Location location = new Location(tuple2.get0(), tuple2.get1());
|
||||
List<BusinessAreaData> businessAreaDataList = cityBusinessArea.getOrDefault(cityId, Collections.emptyList());
|
||||
|
||||
// todo 可以被优化,整个城市过滤太粗糙了
|
||||
return businessAreaDataList.stream()
|
||||
.map(
|
||||
s -> new BusinessArea(s.name, s.areaCode,
|
||||
GeoUtils.distance(s.center, location).intValue()
|
||||
))
|
||||
.filter(businessArea -> businessArea.distance <= radius)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
public static int determineAdminCode(double lonIn, double latIn, CoordinateSystem coordSys) {
|
||||
if (coordSys == null) {
|
||||
coordSys = CoordinateSystem.GCJ02;
|
||||
}
|
||||
Tuple2<Double, Double> gcj02LonLat = GeoUtils.toGCJ02(lonIn, latIn, coordSys);
|
||||
Double lon = gcj02LonLat.get0();
|
||||
Double lat = gcj02LonLat.get1();
|
||||
|
||||
S2LatLng s2LatLng = S2LatLng.fromDegrees(lat, lon);
|
||||
long id = S2CellId.fromLatLng(s2LatLng).parent(GeoTransImpl.min_level).id();
|
||||
long id2 = S2CellId.fromLatLng(s2LatLng).parent(GeoTransImpl.min_level - 2).id();
|
||||
|
||||
if (GeoUtils.outOfChina(lon, lat)) {
|
||||
return -1;
|
||||
}
|
||||
if (boundaryAdminCell.containsKey(id)) {
|
||||
return boundaryAdminCell.getOrDefault(id, -1);
|
||||
}
|
||||
if (boundaryAdminCell.containsKey(id2)) {
|
||||
return boundaryAdminCell.getOrDefault(id2, -1);
|
||||
}
|
||||
|
||||
Set<Long> keys = new HashSet<>();
|
||||
//必须大于2000m,否则会出现格子半径过小选择错误问题
|
||||
int maxLevel = 2000;
|
||||
while (keys.isEmpty() && maxLevel < 200000) {
|
||||
keys = S2Utils.getCellId(s2LatLng, maxLevel, GeoTransImpl.min_level)
|
||||
.stream()
|
||||
.flatMap((Function<Long, Stream<Long>>) aLong ->
|
||||
boundaryIndex.getOrDefault(aLong, Collections.emptyList()).stream())
|
||||
.collect(Collectors.toSet());
|
||||
maxLevel = maxLevel * 2;
|
||||
}
|
||||
|
||||
if (keys.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
List<Tuple2<Tuple4<Tuple2<Double, Double>, Tuple2<Double, Double>, Integer, Boolean>, Double>> lines1 =
|
||||
keys.stream()
|
||||
.map(aLong -> Tuples.of(aLong, new S2CellId(aLong).toLatLng().getEarthDistance(s2LatLng)))
|
||||
.sorted(Comparator.comparing(Tuple2::get1))
|
||||
.limit(5)
|
||||
.flatMap((Function<Tuple2<Long, Double>, Stream<Tuple4<Long, Long, Integer, Boolean>>>)
|
||||
startPoint -> boundaryData.getOrDefault(startPoint.get0(), Collections.emptyList())
|
||||
.stream()
|
||||
.flatMap((Function<Tuple3<Long, Integer, Integer>, Stream<Tuple4<Long, Long, Integer, Boolean>>>)
|
||||
value -> {
|
||||
List<Tuple4<Long, Long, Integer, Boolean>> s = Arrays.asList(
|
||||
Tuples.of(startPoint.get0(), value.get0(), value.get1(), true),
|
||||
Tuples.of(startPoint.get0(), value.get0(), value.get2(), false)
|
||||
);
|
||||
return s.stream();
|
||||
}))
|
||||
.map(line -> {
|
||||
S2LatLng start = new S2CellId(line.get0()).toLatLng();
|
||||
S2LatLng end = new S2CellId(line.get1()).toLatLng();
|
||||
Double dis = LineUtils.pointToLineDis(start.lngDegrees(), start.latDegrees(), end.lngDegrees(), end.latDegrees(), lon, lat);
|
||||
|
||||
return Tuples.of(Tuples.of(Tuples.of(start.lngDegrees(), start.latDegrees()),
|
||||
Tuples.of(end.lngDegrees(), end.latDegrees()),
|
||||
line.get2(), line.get3()
|
||||
), dis);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 取出所有距离最短的线段
|
||||
Double minDis = lines1.stream().map(tuple4DoubleTuple2 -> tuple4DoubleTuple2.get1()).min(Double::compareTo).get();
|
||||
|
||||
List<Tuple4<Tuple2<Double, Double>, Tuple2<Double, Double>, Integer, Boolean>> lines =
|
||||
lines1.stream()
|
||||
.filter(s -> Objects.equals(s.get1(), minDis))
|
||||
.map(tuple2 -> tuple2.get0())
|
||||
.collect(Collectors.groupingBy(Tuple4::get0))
|
||||
.values()
|
||||
.stream()
|
||||
.max(Comparator.comparingInt(List::size))
|
||||
.get();
|
||||
|
||||
|
||||
if (lines.size() == 1) {
|
||||
// 国内海外边界
|
||||
Tuple4<Tuple2<Double, Double>, Tuple2<Double, Double>, Integer, Boolean> line1 = lines.get(0);
|
||||
Tuple2<Double, Double> start = line1.get0();
|
||||
Tuple2<Double, Double> end = line1.get1();
|
||||
// 三点用行列式判断旋转方向
|
||||
double angle = (start.get0() - lon) * (end.get1() - lat) - (end.get0() - lon) * (start.get1() - lat);
|
||||
if ((angle < 0) == line1.get3())
|
||||
return line1.get2();
|
||||
return -1;
|
||||
}
|
||||
if (lines.size() == 2) {
|
||||
// 两条线段,如果终点不同,则一定是国内和海外,并且点到线段距离最短点为起点,终点相同,则为国内两个区域边界
|
||||
Tuple4<Tuple2<Double, Double>, Tuple2<Double, Double>, Integer, Boolean> line1 = lines.get(0);
|
||||
Tuple4<Tuple2<Double, Double>, Tuple2<Double, Double>, Integer, Boolean> line2 = lines.get(1);
|
||||
|
||||
// 终点相同,为国内两个相邻区域,终点不同,为国界线
|
||||
Tuple2<Double, Double> start;
|
||||
if (line1.get1().equals(line2.get1())) {
|
||||
start = line1.get0();
|
||||
} else {
|
||||
start = line2.get1();
|
||||
}
|
||||
|
||||
Tuple2<Double, Double> end = line1.get1();
|
||||
|
||||
// 三点用行列式判断旋转方向
|
||||
double angle = (start.get0() - lon) * (end.get1() - lat) - (end.get0() - lon) * (start.get1() - lat);
|
||||
if ((angle < 0) == line1.get3())
|
||||
return line1.get2();
|
||||
else if (line1.get1().equals(line2.get1()) && !line1.get3().equals(line2.get3()))
|
||||
return line2.get2();
|
||||
else return -1;
|
||||
}
|
||||
//多区域顶点 判断
|
||||
|
||||
|
||||
return lines.stream().collect(Collectors.groupingBy(Tuple4::get2)).entrySet().stream().map(s -> {
|
||||
|
||||
Tuple4<Tuple2<Double, Double>, Tuple2<Double, Double>, Integer, Boolean> line1 =
|
||||
s.getValue().stream().filter(Tuple4::get3).findFirst().get();
|
||||
Tuple4<Tuple2<Double, Double>, Tuple2<Double, Double>, Integer, Boolean> line2 =
|
||||
s.getValue().stream().filter(it -> !it.get3()).findFirst().get();
|
||||
|
||||
Tuple2<Double, Double> start = line2.get1();
|
||||
Tuple2<Double, Double> end = line1.get1();
|
||||
Tuple2<Double, Double> point = line1.get0();
|
||||
|
||||
Double dis1 = LineUtils.lineDis(start.get0(), start.get1(), point.get0(), point.get1());
|
||||
Double dis2 = LineUtils.lineDis(end.get0(), end.get1(), point.get0(), point.get1());
|
||||
if (dis1 > dis2)
|
||||
start = Tuples.of(point.get0() + dis2 / dis1 * (start.get0() - point.get0()), point.get1() + dis2 / dis1 * (start.get1() - point.get1()));
|
||||
else
|
||||
end = Tuples.of(point.get0() + dis1 / dis2 * (end.get0() - point.get0()), point.get1() + dis1 / dis2 * (end.get1() - point.get1()));
|
||||
double angle = (start.get0() - lon) * (end.get1() - lat) - (end.get0() - lon) * (start.get1() - lat);
|
||||
return Tuples.of(s.getKey(), angle);
|
||||
}).min((o1, o2) -> o1.get1().compareTo(o2.get1())).get().get0();
|
||||
}
|
||||
|
||||
public static String getCityLevel(String adcode_or_name) {
|
||||
return cityLevelData.getOrDefault(adcode_or_name, "未知");
|
||||
}
|
||||
|
||||
public static String getCityLevel(Admin admin) {
|
||||
return getCityLevel(String.valueOf(admin.cityCode));
|
||||
}
|
||||
|
||||
public static AdminNode normalizeName(int adcode) {
|
||||
AdminNode adminNode = adminData.get(adcode);
|
||||
if (adminNode != null) {
|
||||
return adminNode;
|
||||
}
|
||||
return streetData.get(adcode);
|
||||
}
|
||||
|
||||
public static List<AdminNode> normalizeName(String name, DistrictLevel level, boolean isFullMatch) {
|
||||
return adminData.values().stream()
|
||||
.filter(it -> it.level.equals(level))
|
||||
.filter(s -> isFullMatch ? s.name.equals(name) : s.shortName.contains(name) || s.name.contains(name))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
public static List<Admin> normalizeName(String provinceIn, String cityIn, String districtIn, String streetIn, boolean isFullMatch) {
|
||||
String province = provinceIn == null || provinceIn.equals("未知") ? "" : provinceIn;
|
||||
String city = cityIn == null || cityIn.equals("未知") ? "" : cityNameMap.getOrDefault(cityIn, cityIn);
|
||||
String district = districtIn == null || districtIn.equals("未知") ? "" : districtNameMap.getOrDefault(districtIn, districtIn);
|
||||
String street = streetIn == null || streetIn.equals("未知") ? "" : streetIn;
|
||||
|
||||
|
||||
List<AdminNode> adminNodes
|
||||
= buildAdminNode(city, district, street, province, isFullMatch);
|
||||
|
||||
|
||||
return adminNodes.stream().map(admin -> {
|
||||
if (admin.level == DistrictLevel.Province) {
|
||||
return Admin.createProvince(admin.name, admin.id, admin.center);
|
||||
}
|
||||
if (admin.level == DistrictLevel.City) {
|
||||
AdminNode province1 = adminData.get(admin.parentId);
|
||||
return Admin.createCity(province1.name, admin.name, province1.id, admin.id, admin.center);
|
||||
}
|
||||
if (admin.level == DistrictLevel.District) {
|
||||
AdminNode city1 = adminData.get(admin.parentId);
|
||||
AdminNode province1 = (city1.level == DistrictLevel.City) ? adminData.get(city1.parentId) : city1;
|
||||
return Admin.createDistrict(province1.name, city1.name, admin.name, province1.id, city1.id, admin.id, admin.center);
|
||||
}
|
||||
if (admin.level == DistrictLevel.Street) {
|
||||
AdminNode district1 = adminData.get(admin.parentId);
|
||||
AdminNode city1 = (district1.level == DistrictLevel.District) ? adminData.get(district1.parentId) : district1;
|
||||
AdminNode province1 = (city1.level == DistrictLevel.City) ? adminData.get(city1.parentId) : city1;
|
||||
return Admin.createStreet(province1.name, city1.name, district1.name, admin.name, province1.id, city1.id, district1.id, admin.id, admin.center);
|
||||
}
|
||||
return Admin.createOversea();
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
private static List<AdminNode> buildAdminNode(String city, String district, String street, String province, boolean isFullMatch) {
|
||||
List<AdminNode> provinceAd = adminData.values().stream().filter(s -> s.level == (DistrictLevel.Province)).filter(s -> StringUtils.isEmpty(province) || s.name.equals(province) || (!isFullMatch && s.shortName.equals(province))).collect(Collectors.toList());
|
||||
|
||||
if (StringUtils.isEmpty(city) && StringUtils.isEmpty(district) && StringUtils.isEmpty(street)) {
|
||||
return provinceAd;
|
||||
}
|
||||
List<AdminNode> cityAd = provinceAd.stream()
|
||||
.flatMap(s -> s.children.stream().map(adCode -> adminData.getOrDefault(adCode, streetData.get(adCode))))
|
||||
.filter(s ->
|
||||
// todo 原文这里的逻辑组合太复杂了,先保持原样看下情况
|
||||
(!(s.level == (DistrictLevel.City)))
|
||||
|| (s.level == (DistrictLevel.City))
|
||||
&& (
|
||||
StringUtils.isEmpty(city)
|
||||
|| s.name.equals(city)
|
||||
|| (!isFullMatch && s.shortName.equals(city))
|
||||
)
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
if (cityAd.isEmpty()) {
|
||||
return provinceAd;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(district) && StringUtils.isEmpty(street)) {
|
||||
return cityAd.stream()
|
||||
.filter(s -> s.level == (DistrictLevel.Province) || s.level == (DistrictLevel.City))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<AdminNode> districtAd = cityAd.stream().flatMap(s -> {
|
||||
if (s.level == (DistrictLevel.City) && StringUtils.isEmpty(street)) {
|
||||
return s.children.stream().map(adCode -> adminData.get(adCode));
|
||||
} else if (s.level == (DistrictLevel.City)) {
|
||||
return s.children.stream().map(adCode -> adminData.getOrDefault(adCode, streetData.get(adCode)));
|
||||
} else {
|
||||
return Stream.of(s);
|
||||
}
|
||||
})
|
||||
.filter(s -> s != null)
|
||||
.filter(s -> (s.level != (DistrictLevel.District)) ||
|
||||
s.level == (DistrictLevel.District) &&
|
||||
(StringUtils.isEmpty(district) || s.name.equals(district) || (!isFullMatch && s.shortName.equals(district)))
|
||||
).collect(Collectors.toList());
|
||||
|
||||
if (districtAd.isEmpty()) {
|
||||
return cityAd;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(street)) {
|
||||
return
|
||||
districtAd.stream().filter(
|
||||
s -> s.level == (DistrictLevel.Province) || s.level == (DistrictLevel.City) || s.level == (DistrictLevel.District)
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<AdminNode> streetAd = districtAd.stream().flatMap(s -> {
|
||||
if (s.level == (DistrictLevel.District)) {
|
||||
return s.children.stream().map(adCode -> streetData.get(adCode));
|
||||
} else {
|
||||
return Stream.of(s);
|
||||
}
|
||||
}).filter(s -> s.name.equals(street) || (!isFullMatch && s.shortName.equals(street)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (streetAd.isEmpty()) {
|
||||
return districtAd;
|
||||
}
|
||||
return streetAd;
|
||||
}
|
||||
|
||||
|
||||
public static Admin determineAdmin(double lon, double lat, boolean needStreet, CoordinateSystem coordSys) {
|
||||
Tuple2<Double, Double> wgs84LonLat = GeoUtils.toWGS84(lon, lat, coordSys);
|
||||
int code = GeoTransImpl.determineAdminCode(wgs84LonLat.get0(), wgs84LonLat.get1(), null);
|
||||
if (code == -1) {
|
||||
return Admin.createOversea();
|
||||
}
|
||||
AdminNode district = adminData.get(code);
|
||||
AdminNode city = (district.level == DistrictLevel.District) ? adminData.get(district.parentId) : district;
|
||||
AdminNode province = (city.level == DistrictLevel.City) ? adminData.get(city.parentId) : city;
|
||||
|
||||
int streetCode = 0;
|
||||
String streetName = "";
|
||||
|
||||
if (needStreet) {
|
||||
List<Integer> children = district.children;
|
||||
if (children != null && !children.isEmpty()) {
|
||||
AdminNode minAdminNode = null;
|
||||
double minDistance = Double.MAX_VALUE;
|
||||
for (Integer integer : children) {
|
||||
AdminNode adminNode = streetData.get(integer);
|
||||
if (minAdminNode == null) {
|
||||
minAdminNode = adminNode;
|
||||
minDistance = GeoUtils.distance(adminNode.center, new Location(wgs84LonLat.get0(), wgs84LonLat.get1()));
|
||||
continue;
|
||||
}
|
||||
Double distance = GeoUtils.distance(adminNode.center, new Location(wgs84LonLat.get0(), wgs84LonLat.get1()));
|
||||
if (distance < minDistance) {
|
||||
minAdminNode = adminNode;
|
||||
minDistance = distance;
|
||||
}
|
||||
}
|
||||
streetCode = minAdminNode.id;
|
||||
streetName = minAdminNode.name;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (streetCode > 0) {
|
||||
return Admin.createStreet(province.name, city.name, district.name, streetName, province.id, city.id, district.id, streetCode, district.center);
|
||||
}
|
||||
return Admin.createDistrict(province.name, city.name, district.name, province.id, city.id, district.id, district.center);
|
||||
}
|
||||
}
|
244
src/main/java/com/dengxq/lnglat2Geo/entity/Admin.java
Normal file
244
src/main/java/com/dengxq/lnglat2Geo/entity/Admin.java
Normal file
@ -0,0 +1,244 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
import com.dengxq.lnglat2Geo.utils.AdminUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Admin {
|
||||
|
||||
private static final String CHINA_NAME = "中国";
|
||||
private static final String CHINA_ID = "CN";
|
||||
private static final String OVERSEA_NAME_VAL = "海外";
|
||||
private static final String UNKNOWN_NAME_VAL = "未知";
|
||||
private static final int UNKNOWN_ID_VAL = -1;
|
||||
private static final Location UNKNOWN_LOCATION_VAL = null;
|
||||
|
||||
|
||||
public String country;
|
||||
public String province;
|
||||
public String city;
|
||||
public String district;
|
||||
public String town;
|
||||
public String level;
|
||||
public String countryCode;
|
||||
public int provinceCode;
|
||||
public int cityCode;
|
||||
public int districtCode;
|
||||
public int townCode;
|
||||
public Location center;
|
||||
|
||||
public Admin(String country, String province, String city, String district, String town, String level, String countryCode, int provinceCode, int cityCode, int districtCode, int townCode, Location center) {
|
||||
this.country = country;
|
||||
this.province = province;
|
||||
this.city = city;
|
||||
this.district = district;
|
||||
this.town = town;
|
||||
this.level = level;
|
||||
this.countryCode = countryCode;
|
||||
this.provinceCode = provinceCode;
|
||||
this.cityCode = cityCode;
|
||||
this.districtCode = districtCode;
|
||||
this.townCode = townCode;
|
||||
this.center = center;
|
||||
}
|
||||
|
||||
|
||||
public Admin() {
|
||||
}
|
||||
|
||||
public boolean hasCenter() {
|
||||
return center != Admin.UNKNOWN_LOCATION_VAL;
|
||||
}
|
||||
|
||||
public boolean hasProvince() {
|
||||
return !Objects.equals(province, Admin.UNKNOWN_NAME_VAL);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasCity() {
|
||||
return !Objects.equals(city, Admin.UNKNOWN_NAME_VAL);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasDistrict() {
|
||||
return !Objects.equals(district, Admin.UNKNOWN_NAME_VAL);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasCityId() {
|
||||
return cityCode != Admin.UNKNOWN_ID_VAL;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasDistrictId() {
|
||||
return districtCode != Admin.UNKNOWN_ID_VAL;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasTown() {
|
||||
return !Objects.equals(town, Admin.UNKNOWN_NAME_VAL);
|
||||
}
|
||||
|
||||
|
||||
public String shortProvince() {
|
||||
return AdminUtils.shortProvince(province);
|
||||
}
|
||||
|
||||
public String shortCity() {
|
||||
return AdminUtils.shortCity(city);
|
||||
}
|
||||
|
||||
public Admin toShort() {
|
||||
return new Admin(country,
|
||||
AdminUtils.shortProvince(province),
|
||||
AdminUtils.shortCity(city),
|
||||
AdminUtils.shortDistrict(district),
|
||||
AdminUtils.shortStreet(town),
|
||||
level, countryCode, provinceCode, cityCode, districtCode, townCode, center
|
||||
);
|
||||
}
|
||||
// def toNameString: String = s"$country${if (hasProvince) province else ""}
|
||||
// ${if (hasCity) city else ""}
|
||||
// ${if (hasDistrict) district else ""}
|
||||
// ${if (hasTown) town else ""}"
|
||||
|
||||
public String toNameString() {
|
||||
StringBuilder sb = new StringBuilder(country);
|
||||
if (hasProvince()) {
|
||||
sb.append(province);
|
||||
}
|
||||
if (hasCity()) {
|
||||
sb.append(city);
|
||||
}
|
||||
if (hasDistrict()) {
|
||||
sb.append(district);
|
||||
}
|
||||
if (hasTown()) {
|
||||
sb.append(town);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toNameString();
|
||||
}
|
||||
|
||||
public static Admin createOversea() {
|
||||
return new Admin(OVERSEA_NAME_VAL,
|
||||
OVERSEA_NAME_VAL,
|
||||
OVERSEA_NAME_VAL,
|
||||
OVERSEA_NAME_VAL,
|
||||
OVERSEA_NAME_VAL,
|
||||
AdminLevel.Oversea,
|
||||
"",
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_LOCATION_VAL);
|
||||
}
|
||||
|
||||
public static Admin createCountry(String country, String countryID, Location center) {
|
||||
return new Admin(country,
|
||||
UNKNOWN_NAME_VAL,
|
||||
UNKNOWN_NAME_VAL,
|
||||
UNKNOWN_NAME_VAL,
|
||||
UNKNOWN_NAME_VAL,
|
||||
AdminLevel.Country,
|
||||
countryID,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
center);
|
||||
}
|
||||
|
||||
public static Admin createProvince(String province, int provinceId, Location center) {
|
||||
return new Admin(
|
||||
CHINA_NAME,
|
||||
province,
|
||||
UNKNOWN_NAME_VAL,
|
||||
UNKNOWN_NAME_VAL,
|
||||
UNKNOWN_NAME_VAL,
|
||||
AdminLevel.Province,
|
||||
CHINA_ID,
|
||||
provinceId,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
center
|
||||
);
|
||||
}
|
||||
|
||||
public static Admin createCity(String province, String city, int provinceId, int cityId, Location center) {
|
||||
return new Admin(
|
||||
CHINA_NAME,
|
||||
province,
|
||||
city,
|
||||
UNKNOWN_NAME_VAL,
|
||||
UNKNOWN_NAME_VAL,
|
||||
AdminLevel.City,
|
||||
CHINA_ID,
|
||||
provinceId,
|
||||
cityId,
|
||||
UNKNOWN_ID_VAL,
|
||||
UNKNOWN_ID_VAL,
|
||||
center
|
||||
);
|
||||
}
|
||||
|
||||
public static Admin createProvincialCity(String province, String city, int provinceId, int cityId, Location center) {
|
||||
return new Admin(
|
||||
CHINA_NAME,
|
||||
province,
|
||||
city,
|
||||
city,
|
||||
UNKNOWN_NAME_VAL,
|
||||
AdminLevel.ProvincialCity,
|
||||
CHINA_ID,
|
||||
provinceId,
|
||||
cityId,
|
||||
cityId,
|
||||
UNKNOWN_ID_VAL,
|
||||
center
|
||||
);
|
||||
}
|
||||
|
||||
public static Admin createDistrict(String province, String city, String district,
|
||||
int provinceId, int cityId, int districtId, Location center) {
|
||||
return new Admin(
|
||||
CHINA_NAME,
|
||||
province,
|
||||
city,
|
||||
district,
|
||||
UNKNOWN_NAME_VAL,
|
||||
AdminLevel.District,
|
||||
CHINA_ID,
|
||||
provinceId,
|
||||
cityId,
|
||||
districtId,
|
||||
UNKNOWN_ID_VAL,
|
||||
center
|
||||
);
|
||||
}
|
||||
|
||||
public static Admin createStreet(String province, String city, String district, String town,
|
||||
int provinceId, int cityId, int districtId, int streetId, Location center) {
|
||||
return new Admin(
|
||||
CHINA_NAME,
|
||||
province,
|
||||
city,
|
||||
district,
|
||||
town,
|
||||
AdminLevel.Street,
|
||||
CHINA_ID,
|
||||
provinceId,
|
||||
cityId,
|
||||
districtId,
|
||||
streetId,
|
||||
center
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class AdminBoundary {
|
||||
public int code;
|
||||
public List<List<Long>> boundary;
|
||||
|
||||
}
|
18
src/main/java/com/dengxq/lnglat2Geo/entity/AdminLevel.java
Normal file
18
src/main/java/com/dengxq/lnglat2Geo/entity/AdminLevel.java
Normal file
@ -0,0 +1,18 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
public class AdminLevel {
|
||||
/** 海外* */
|
||||
public static final String Oversea = "oversea";
|
||||
/** 国家* */
|
||||
public static final String Country = "country";
|
||||
/** 省,自治区* */
|
||||
public static final String Province = "province";
|
||||
/** 地级市* */
|
||||
public static final String City = "city";
|
||||
/** 省辖市(属县级市) see:https://baike.baidu.com/item/省直辖县级行政单位* */
|
||||
public static final String ProvincialCity = "provincialcity";
|
||||
/** 区,县,县级市* */
|
||||
public static final String District = "district";
|
||||
/** 街道* */
|
||||
public static final String Street = "street";
|
||||
}
|
72
src/main/java/com/dengxq/lnglat2Geo/entity/AdminNode.java
Normal file
72
src/main/java/com/dengxq/lnglat2Geo/entity/AdminNode.java
Normal file
@ -0,0 +1,72 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.enums.DistrictLevel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AdminNode {
|
||||
public int id;
|
||||
public String name;
|
||||
public String shortName;
|
||||
public Location center;
|
||||
public DistrictLevel level;
|
||||
public int parentId;
|
||||
public List<Integer> children = new ArrayList<>();
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public void setShortName(String shortName) {
|
||||
this.shortName = shortName;
|
||||
}
|
||||
|
||||
public Location getCenter() {
|
||||
return center;
|
||||
}
|
||||
|
||||
public void setCenter(Location center) {
|
||||
this.center = center;
|
||||
}
|
||||
|
||||
public DistrictLevel getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(DistrictLevel level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public int getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public void setParentId(int parentId) {
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
public List<Integer> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(List<Integer> children) {
|
||||
this.children = children;
|
||||
}
|
||||
}
|
27
src/main/java/com/dengxq/lnglat2Geo/entity/Bound.java
Normal file
27
src/main/java/com/dengxq/lnglat2Geo/entity/Bound.java
Normal file
@ -0,0 +1,27 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
public class Bound {
|
||||
public Location mix;
|
||||
public Location max;
|
||||
|
||||
public Bound(Location mix, Location max) {
|
||||
this.mix = mix;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public Location getMix() {
|
||||
return mix;
|
||||
}
|
||||
|
||||
public Location getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
public void setMix(Location mix) {
|
||||
this.mix = mix;
|
||||
}
|
||||
|
||||
public void setMax(Location max) {
|
||||
this.max = max;
|
||||
}
|
||||
}
|
13
src/main/java/com/dengxq/lnglat2Geo/entity/BusinessArea.java
Normal file
13
src/main/java/com/dengxq/lnglat2Geo/entity/BusinessArea.java
Normal file
@ -0,0 +1,13 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
public class BusinessArea {
|
||||
public String name;
|
||||
public Integer areaCode;
|
||||
public Integer distance;
|
||||
|
||||
public BusinessArea(String name, Integer areaCode, Integer distance) {
|
||||
this.name = name;
|
||||
this.areaCode = areaCode;
|
||||
this.distance = distance;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BusinessAreaData {
|
||||
// 商圈名称
|
||||
public String name;
|
||||
//商圈中心点
|
||||
public Location center;
|
||||
//areaCode 商圈ID
|
||||
public int areaCode;
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BusinessAreaGroup {
|
||||
public int cityAdCode;
|
||||
public List<BusinessAreaData> areas;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BusinessAreaInfo {
|
||||
public Admin admin;
|
||||
public List<BusinessArea> areas;
|
||||
|
||||
public BusinessAreaInfo(Admin admin, List<BusinessArea> areas) {
|
||||
this.admin = admin;
|
||||
this.areas = areas;
|
||||
}
|
||||
}
|
14
src/main/java/com/dengxq/lnglat2Geo/entity/CellAdmin.java
Normal file
14
src/main/java/com/dengxq/lnglat2Geo/entity/CellAdmin.java
Normal file
@ -0,0 +1,14 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class CellAdmin {
|
||||
public int adCode;
|
||||
public long cellId;
|
||||
|
||||
|
||||
}
|
21
src/main/java/com/dengxq/lnglat2Geo/entity/Location.java
Normal file
21
src/main/java/com/dengxq/lnglat2Geo/entity/Location.java
Normal file
@ -0,0 +1,21 @@
|
||||
package com.dengxq.lnglat2Geo.entity;
|
||||
|
||||
import com.google.common.geometry.S2CellId;
|
||||
import com.google.common.geometry.S2LatLng;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Location {
|
||||
public Double lng;
|
||||
public Double lat;
|
||||
private long id;
|
||||
|
||||
public Location(Double lng, Double lat) {
|
||||
this.lng = lng;
|
||||
this.lat = lat;
|
||||
id = S2CellId.fromLatLng(S2LatLng.fromDegrees(lat, lng)).id();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.dengxq.lnglat2Geo.entity.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public enum Azimuth {
|
||||
North(0),
|
||||
NorthEast(45),
|
||||
East(90),
|
||||
SouthEast(135),
|
||||
South(180),
|
||||
SouthWest(225),
|
||||
West(270),
|
||||
NorthWest(315);
|
||||
|
||||
@Getter
|
||||
private int code;
|
||||
|
||||
Azimuth(int value) {
|
||||
this.code = value;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.dengxq.lnglat2Geo.entity.enums;
|
||||
|
||||
public enum CoordinateSystem {
|
||||
WGS84, // GPS 坐标系
|
||||
GCJ02, // 国测局坐标系(火星坐标系)
|
||||
BD09,// 百度坐标系
|
||||
BJ54,// 北京54坐标系
|
||||
XIAN80,// 西安80坐标系
|
||||
CGCS2000,// 2000国家大地坐标系
|
||||
XYZ,// 笛卡尔坐标系
|
||||
MERCATOR;// 墨卡托坐标系
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.dengxq.lnglat2Geo.entity.enums;
|
||||
|
||||
public enum DistrictLevel {
|
||||
/**
|
||||
* 街道*
|
||||
*/
|
||||
Country,
|
||||
/**
|
||||
* 省,自治区*
|
||||
*/
|
||||
Province,
|
||||
/**
|
||||
* 地级市*
|
||||
*/
|
||||
City,
|
||||
/**
|
||||
* 区,县,县级市*
|
||||
*/
|
||||
District,
|
||||
Street
|
||||
}
|
414
src/main/java/com/dengxq/lnglat2Geo/loader/BinLoader.java
Normal file
414
src/main/java/com/dengxq/lnglat2Geo/loader/BinLoader.java
Normal file
@ -0,0 +1,414 @@
|
||||
package com.dengxq.lnglat2Geo.loader;
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.*;
|
||||
import com.dengxq.lnglat2Geo.entity.enums.DistrictLevel;
|
||||
import com.dengxq.lnglat2Geo.utils.Leb128;
|
||||
import com.dengxq.lnglat2Geo.utils.Md5Util;
|
||||
import com.speedment.common.tuple.Tuple3;
|
||||
import com.speedment.common.tuple.Tuples;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 二进制格式加载,相比json更快,更节省空间,
|
||||
* 但是文件内容无法查看
|
||||
*/
|
||||
public class BinLoader implements ILoader {
|
||||
private static final int M = (1 << 20);
|
||||
|
||||
private String sourcePath = "data.bin";
|
||||
|
||||
public BinLoader(String sourcePath) {
|
||||
this.sourcePath = sourcePath;
|
||||
}
|
||||
|
||||
public BinLoader() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoData load() {
|
||||
try (InputStream stream = BinLoader.class.getClassLoader().getResourceAsStream(sourcePath)) {
|
||||
if (stream == null) {
|
||||
throw new RuntimeException("can not find data resource: " + sourcePath);
|
||||
}
|
||||
byte[] bytes = IOUtils.toByteArray(stream);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
GeoData geoData = new GeoData();
|
||||
geoData.setAdminData(readAdminNodes(buffer));
|
||||
geoData.setStreetData(readAdminNodes(buffer));
|
||||
geoData.setAdminBoundaries(readAdminBoundaries(buffer));
|
||||
geoData.setAreaGroups(readAreaGroups(buffer));
|
||||
geoData.setCellAdmins(readCellAdmins(buffer));
|
||||
geoData.setCityLevel(readMap(buffer));
|
||||
geoData.setCountryCode(readMap(buffer));
|
||||
|
||||
// 对于扩展数据,我们默认是不会打入到二进制中,因为他会导致我们文件太大
|
||||
// 但是我们会把他缓存到缓存文件中,这样多次运行就会比较快
|
||||
String dataMd5 = Md5Util.getHashWithInputStream(new ByteArrayInputStream(bytes));
|
||||
handleExtension(geoData, dataMd5);
|
||||
return geoData;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleExtension(GeoData geoData, String md5) throws IOException {
|
||||
String userHome = System.getProperty("user.home");
|
||||
File base;
|
||||
if (userHome != null && !userHome.trim().isEmpty()) {
|
||||
base = new File(userHome);
|
||||
} else {
|
||||
base = new File(".");
|
||||
}
|
||||
File extensionPart = new File(base, md5 + ".xcgeo");
|
||||
if (extensionPart.exists()) {
|
||||
// 如果存在缓存,那么把缓存加载到内存
|
||||
loadExtension(geoData, extensionPart);
|
||||
} else {
|
||||
new Thread("createExtension") {
|
||||
@Override
|
||||
public void run() {
|
||||
// 如果没有缓存文件,那么使用新的线程建立缓存文件
|
||||
storeExtension(geoData, extensionPart);
|
||||
}
|
||||
}.start();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void loadExtension(GeoData geoData, File file) throws IOException {
|
||||
try (FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel()) {
|
||||
MappedByteBuffer buffer = fileChannel
|
||||
.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
|
||||
geoData.setRuntimeBoundaryData(readRuntimeBoundaryData(buffer));
|
||||
geoData.setRuntimeBoundaryIndex(readRuntimeBoundaryIndex(buffer));
|
||||
geoData.setRuntimeBoundaryAdminCell(readRuntimeBoundaryAdminCell(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
private void storeExtension(GeoData geoData, File file) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(300 * M);
|
||||
writeRuntimeBoundaryData(geoData.getOrCreateRuntimeBoundaryData(), buffer);
|
||||
writeRuntimeBoundaryIndex(geoData.getOrCreateRuntimeBoundaryIndex(), buffer);
|
||||
writeRuntimeBoundaryAdminCell(geoData.getOrCreateRuntimeBoundaryAdminCell(), buffer);
|
||||
saveByteBuffer(buffer, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(File path, GeoData geoData) {
|
||||
// dump不需要考虑性能,分配50M的内存空间
|
||||
ByteBuffer buffer = ByteBuffer.allocate(70 * M);
|
||||
writeAdminNodes(geoData.getAdminData(), buffer);
|
||||
writeAdminNodes(geoData.getStreetData(), buffer);
|
||||
writeAdminBoundaries(geoData.getAdminBoundaries(), buffer);
|
||||
writeAreaGroups(geoData.getAreaGroups(), buffer);
|
||||
writeCellAdmins(geoData.getCellAdmins(), buffer);
|
||||
writeMap(geoData.getCityLevel(), buffer);
|
||||
writeMap(geoData.getCountryCode(), buffer);
|
||||
|
||||
saveByteBuffer(buffer, path);
|
||||
}
|
||||
|
||||
private static void saveByteBuffer(ByteBuffer buffer, File file) {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
|
||||
buffer.flip();
|
||||
byte[] bytes = localBuff(4096);
|
||||
|
||||
while (buffer.hasRemaining()) {
|
||||
int count = Math.min(bytes.length, buffer.remaining());
|
||||
buffer.get(bytes, 0, count);
|
||||
fileOutputStream.write(bytes, 0, count);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final ThreadLocal<byte[]> buffLocal = new ThreadLocal<>();
|
||||
|
||||
private static byte[] localBuff(int len) {
|
||||
if (len < 1024) {
|
||||
len = 1024;
|
||||
}
|
||||
byte[] bytes = buffLocal.get();
|
||||
if (bytes != null && bytes.length >= len) {
|
||||
return bytes;
|
||||
}
|
||||
bytes = new byte[len];
|
||||
buffLocal.set(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
|
||||
private static String readString(ByteBuffer buffer) {
|
||||
int len = Leb128.readUnsignedLeb128(buffer);
|
||||
if (len == 0) {
|
||||
return "";
|
||||
}
|
||||
// 读需要使用缓存的内存变量,减少gc消耗,写不需要关注,
|
||||
// 这里提供了一个4k的缓存
|
||||
byte[] buf = localBuff(len);
|
||||
buffer.get(buf, 0, len);
|
||||
return new String(buf, 0, len, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static void writeString(ByteBuffer buffer, String str) {
|
||||
if (str == null) {
|
||||
str = "";
|
||||
}
|
||||
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
|
||||
Leb128.writeUnsignedLeb128(buffer, bytes.length);
|
||||
if (bytes.length > 0) {
|
||||
buffer.put(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAdminNodes(List<AdminNode> adminNodes, ByteBuffer buffer) {
|
||||
Leb128.writeUnsignedLeb128(buffer, adminNodes.size());
|
||||
for (AdminNode adminNode : adminNodes) {
|
||||
Leb128.writeUnsignedLeb128(buffer, adminNode.id);
|
||||
writeString(buffer, adminNode.name);
|
||||
writeString(buffer, adminNode.shortName);
|
||||
|
||||
// 经纬度,我们使用float编码,因为实际上看起来,目前的数据应该在float上
|
||||
buffer.putFloat(adminNode.center.lng.floatValue());
|
||||
buffer.putFloat(adminNode.center.lat.floatValue());
|
||||
|
||||
buffer.put((byte) adminNode.level.ordinal());
|
||||
Leb128.writeUnsignedLeb128(buffer, adminNode.parentId);
|
||||
|
||||
List<Integer> children = adminNode.children;
|
||||
Leb128.writeUnsignedLeb128(buffer, children.size());
|
||||
for (Integer integer : children) {
|
||||
Leb128.writeSignedLeb128(buffer, integer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<AdminNode> readAdminNodes(ByteBuffer buffer) {
|
||||
int size = Leb128.readUnsignedLeb128(buffer);
|
||||
DistrictLevel[] districtLevels = DistrictLevel.values();
|
||||
ArrayList<AdminNode> adminNodes = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
AdminNode adminNode = new AdminNode();
|
||||
adminNodes.add(adminNode);
|
||||
adminNode.setId(Leb128.readUnsignedLeb128(buffer));
|
||||
adminNode.setName(readString(buffer));
|
||||
adminNode.setShortName(readString(buffer));
|
||||
adminNode.setCenter(new Location((double) buffer.getFloat(), (double) buffer.getFloat()));
|
||||
adminNode.setLevel(districtLevels[buffer.get()]);
|
||||
adminNode.setParentId(Leb128.readUnsignedLeb128(buffer));
|
||||
|
||||
int childrenSize = Leb128.readUnsignedLeb128(buffer);
|
||||
ArrayList<Integer> children = new ArrayList<>(childrenSize);
|
||||
adminNode.setChildren(children);
|
||||
for (int j = 0; j < childrenSize; j++) {
|
||||
children.add(Leb128.readSignedLeb128(buffer));
|
||||
}
|
||||
}
|
||||
return adminNodes;
|
||||
}
|
||||
|
||||
|
||||
private void writeAdminBoundaries(List<AdminBoundary> adminBoundaries, ByteBuffer buffer) {
|
||||
Leb128.writeUnsignedLeb128(buffer, adminBoundaries.size());
|
||||
for (AdminBoundary adminBoundary : adminBoundaries) {
|
||||
//code
|
||||
Leb128.writeSignedLeb128(buffer, adminBoundary.code);
|
||||
List<List<Long>> boundary = adminBoundary.boundary;
|
||||
Leb128.writeUnsignedLeb128(buffer, boundary.size());
|
||||
for (List<Long> line : boundary) {
|
||||
Leb128.writeUnsignedLeb128(buffer, line.size());
|
||||
for (Long point : line) {
|
||||
buffer.putLong(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<AdminBoundary> readAdminBoundaries(ByteBuffer buffer) {
|
||||
int size = Leb128.readUnsignedLeb128(buffer);
|
||||
ArrayList<AdminBoundary> adminBoundaries = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
AdminBoundary adminBoundary = new AdminBoundary();
|
||||
adminBoundaries.add(adminBoundary);
|
||||
adminBoundary.setCode(Leb128.readSignedLeb128(buffer));
|
||||
|
||||
int boundarySize = Leb128.readUnsignedLeb128(buffer);
|
||||
ArrayList<List<Long>> boundary = new ArrayList<>(boundarySize);
|
||||
adminBoundary.setBoundary(boundary);
|
||||
for (int j = 0; j < boundarySize; j++) {
|
||||
int lineSize = Leb128.readUnsignedLeb128(buffer);
|
||||
ArrayList<Long> line = new ArrayList<>(lineSize);
|
||||
boundary.add(line);
|
||||
for (int z = 0; z < lineSize; z++) {
|
||||
line.add(buffer.getLong());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return adminBoundaries;
|
||||
}
|
||||
|
||||
|
||||
private void writeAreaGroups(List<BusinessAreaGroup> areaGroups, ByteBuffer buffer) {
|
||||
Leb128.writeUnsignedLeb128(buffer, areaGroups.size());
|
||||
for (BusinessAreaGroup businessAreaGroup : areaGroups) {
|
||||
Leb128.writeSignedLeb128(buffer, businessAreaGroup.cityAdCode);
|
||||
List<BusinessAreaData> areas = businessAreaGroup.areas;
|
||||
Leb128.writeUnsignedLeb128(buffer, areas.size());
|
||||
for (BusinessAreaData businessAreaData : areas) {
|
||||
writeString(buffer, businessAreaData.name);
|
||||
buffer.putFloat(businessAreaData.center.lng.floatValue());
|
||||
buffer.putFloat(businessAreaData.center.lat.floatValue());
|
||||
Leb128.writeSignedLeb128(buffer, businessAreaData.areaCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<BusinessAreaGroup> readAreaGroups(ByteBuffer buffer) {
|
||||
int size = Leb128.readUnsignedLeb128(buffer);
|
||||
List<BusinessAreaGroup> ret = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
BusinessAreaGroup areaGroup = new BusinessAreaGroup();
|
||||
ret.add(areaGroup);
|
||||
areaGroup.setCityAdCode(Leb128.readSignedLeb128(buffer));
|
||||
int businessAreaDataSize = Leb128.readUnsignedLeb128(buffer);
|
||||
List<BusinessAreaData> areas = new ArrayList<>(businessAreaDataSize);
|
||||
areaGroup.setAreas(areas);
|
||||
for (int j = 0; j < businessAreaDataSize; j++) {
|
||||
BusinessAreaData businessAreaData = new BusinessAreaData();
|
||||
areas.add(businessAreaData);
|
||||
businessAreaData.setName(readString(buffer));
|
||||
businessAreaData.setCenter(new Location((double) buffer.getFloat(), (double) buffer.getFloat()));
|
||||
businessAreaData.setAreaCode(Leb128.readSignedLeb128(buffer));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void writeCellAdmins(List<CellAdmin> cellAdmins, ByteBuffer buffer) {
|
||||
Leb128.writeUnsignedLeb128(buffer, cellAdmins.size());
|
||||
for (CellAdmin cellAdmin : cellAdmins) {
|
||||
Leb128.writeSignedLeb128(buffer, cellAdmin.adCode);
|
||||
buffer.putLong(cellAdmin.cellId);
|
||||
}
|
||||
}
|
||||
|
||||
private List<CellAdmin> readCellAdmins(ByteBuffer buffer) {
|
||||
int size = Leb128.readUnsignedLeb128(buffer);
|
||||
List<CellAdmin> ret = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
CellAdmin cellAdmin = new CellAdmin(Leb128.readSignedLeb128(buffer),
|
||||
buffer.getLong());
|
||||
ret.add(cellAdmin);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private void writeMap(Map<String, String> map, ByteBuffer buffer) {
|
||||
Leb128.writeUnsignedLeb128(buffer, map.size());
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
writeString(buffer, entry.getKey());
|
||||
writeString(buffer, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> readMap(ByteBuffer buffer) {
|
||||
Map<String, String> ret = new HashMap<>();
|
||||
int size = Leb128.readUnsignedLeb128(buffer);
|
||||
for (int i = 0; i < size; i++) {
|
||||
ret.put(readString(buffer), readString(buffer));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 请注意,runtime文件不进行leb128,因为runtime大约会在200M左右,对他进行压缩没有意义
|
||||
// 反而leb128会有一些计算出现
|
||||
private void writeRuntimeBoundaryData(Map<Long, List<Tuple3<Long, Integer, Integer>>> data, ByteBuffer buffer) {
|
||||
buffer.putInt(data.size());
|
||||
for (Map.Entry<Long, List<Tuple3<Long, Integer, Integer>>> entry : data.entrySet()) {
|
||||
Long key = entry.getKey();
|
||||
buffer.putLong(key);
|
||||
List<Tuple3<Long, Integer, Integer>> value = entry.getValue();
|
||||
buffer.putInt(value.size());
|
||||
for (Tuple3<Long, Integer, Integer> tuple3 : value) {
|
||||
buffer.putLong(tuple3.get0());
|
||||
buffer.putInt(tuple3.get1());
|
||||
buffer.putInt(tuple3.get2());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, List<Tuple3<Long, Integer, Integer>>> readRuntimeBoundaryData(ByteBuffer buffer) {
|
||||
int mapSize = buffer.getInt();
|
||||
Map<Long, List<Tuple3<Long, Integer, Integer>>> ret = new HashMap<>(mapSize);
|
||||
for (int i = 0; i < mapSize; i++) {
|
||||
long key = buffer.getLong();
|
||||
int listSize = buffer.getInt();
|
||||
List<Tuple3<Long, Integer, Integer>> line = new ArrayList<>(listSize);
|
||||
ret.put(key, line);
|
||||
|
||||
for (int j = 0; j < listSize; j++) {
|
||||
line.add(Tuples.of(buffer.getLong(), buffer.getInt(), buffer.getInt()));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void writeRuntimeBoundaryIndex(Map<Long, List<Long>> data, ByteBuffer buffer) {
|
||||
buffer.putInt(data.size());
|
||||
for (Map.Entry<Long, List<Long>> entry : data.entrySet()) {
|
||||
Long key = entry.getKey();
|
||||
buffer.putLong(key);
|
||||
List<Long> value = entry.getValue();
|
||||
buffer.putInt(value.size());
|
||||
for (Long aLong : value) {
|
||||
buffer.putLong(aLong);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, List<Long>> readRuntimeBoundaryIndex(ByteBuffer buffer) {
|
||||
int mapSize = buffer.getInt();
|
||||
Map<Long, List<Long>> ret = new HashMap<>(mapSize);
|
||||
for (int i = 0; i < mapSize; i++) {
|
||||
long key = buffer.getLong();
|
||||
int listSize = buffer.getInt();
|
||||
List<Long> line = new ArrayList<>(listSize);
|
||||
ret.put(key, line);
|
||||
for (int j = 0; j < listSize; j++) {
|
||||
line.add(buffer.getLong());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void writeRuntimeBoundaryAdminCell(Map<Long, Integer> data, ByteBuffer buffer) {
|
||||
buffer.putInt(data.size());
|
||||
for (Map.Entry<Long, Integer> entry : data.entrySet()) {
|
||||
buffer.putLong(entry.getKey());
|
||||
buffer.putInt(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, Integer> readRuntimeBoundaryAdminCell(ByteBuffer buffer) {
|
||||
int mapSize = buffer.getInt();
|
||||
Map<Long, Integer> ret = new HashMap<>(mapSize);
|
||||
for (int i = 0; i < mapSize; i++) {
|
||||
ret.put(buffer.getLong(), buffer.getInt());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
94
src/main/java/com/dengxq/lnglat2Geo/loader/GeoData.java
Normal file
94
src/main/java/com/dengxq/lnglat2Geo/loader/GeoData.java
Normal file
@ -0,0 +1,94 @@
|
||||
package com.dengxq.lnglat2Geo.loader;
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.AdminBoundary;
|
||||
import com.dengxq.lnglat2Geo.entity.AdminNode;
|
||||
import com.dengxq.lnglat2Geo.entity.BusinessAreaGroup;
|
||||
import com.dengxq.lnglat2Geo.entity.CellAdmin;
|
||||
import com.dengxq.lnglat2Geo.utils.Utils;
|
||||
import com.speedment.common.tuple.Tuple3;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 离线数据包,包括行政区折线图、行政等级划分、商圈、城市编码、城市级别等
|
||||
* <p>
|
||||
* 他可以由二进制或者json两种格式转换加载,并且可以序列化到二进制和json两种格式,
|
||||
* 其中二进制格式将会伴随发布包发布,作为默认的离线数据资源,
|
||||
* json格式则主要用于调试和升级二进制格式使用
|
||||
*/
|
||||
@Data
|
||||
public class GeoData {
|
||||
|
||||
/**
|
||||
* 行政区域数据
|
||||
*/
|
||||
private List<AdminNode> adminData;
|
||||
|
||||
/**
|
||||
* 街道数据
|
||||
*/
|
||||
private List<AdminNode> streetData;
|
||||
|
||||
/**
|
||||
* 多边形边界线,规定每个区域的范围
|
||||
*/
|
||||
private List<AdminBoundary> adminBoundaries;
|
||||
|
||||
|
||||
/**
|
||||
* 商圈
|
||||
*/
|
||||
private List<BusinessAreaGroup> areaGroups;
|
||||
|
||||
/**
|
||||
* s2到行政编码的缓存,他不是必须的,但是可以提高计算速度
|
||||
*/
|
||||
private List<CellAdmin> cellAdmins;
|
||||
|
||||
/**
|
||||
* 城市级别
|
||||
*/
|
||||
private Map<String, String> cityLevel;
|
||||
|
||||
/**
|
||||
* 国家编码
|
||||
*/
|
||||
private Map<String, String> countryCode;
|
||||
|
||||
// 以下为extension数据,他是根据上面的字段计算而来,因为他们的计算比较耗时,我们把计算好的数据缓存到这里,
|
||||
// json 格式不序列化这些字段,bin需要序列化这些字段
|
||||
private Map<Long, List<Tuple3<Long, Integer, Integer>>> runtimeBoundaryData;
|
||||
|
||||
private Map<Long, List<Long>> runtimeBoundaryIndex;
|
||||
|
||||
private Map<Long, Integer> runtimeBoundaryAdminCell;
|
||||
|
||||
public void clearRuntime() {
|
||||
runtimeBoundaryData = null;
|
||||
runtimeBoundaryIndex = null;
|
||||
runtimeBoundaryAdminCell = null;
|
||||
}
|
||||
|
||||
public Map<Long, List<Tuple3<Long, Integer, Integer>>> getOrCreateRuntimeBoundaryData() {
|
||||
if (runtimeBoundaryData == null) {
|
||||
runtimeBoundaryData = Utils.parseBoundaryData(adminBoundaries);
|
||||
}
|
||||
return runtimeBoundaryData;
|
||||
}
|
||||
|
||||
public Map<Long, List<Long>> getOrCreateRuntimeBoundaryIndex() {
|
||||
if (runtimeBoundaryIndex == null) {
|
||||
runtimeBoundaryIndex = Utils.buildBoundaryIndex(getOrCreateRuntimeBoundaryData());
|
||||
}
|
||||
return runtimeBoundaryIndex;
|
||||
}
|
||||
|
||||
public Map<Long, Integer> getOrCreateRuntimeBoundaryAdminCell() {
|
||||
if (runtimeBoundaryAdminCell == null) {
|
||||
runtimeBoundaryAdminCell = Utils.buildBoundaryAdminCell(cellAdmins);
|
||||
}
|
||||
return runtimeBoundaryAdminCell;
|
||||
}
|
||||
}
|
52
src/main/java/com/dengxq/lnglat2Geo/loader/ILoader.java
Normal file
52
src/main/java/com/dengxq/lnglat2Geo/loader/ILoader.java
Normal file
@ -0,0 +1,52 @@
|
||||
package com.dengxq.lnglat2Geo.loader;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 数据加载和dump的顶层,支持从json文件和bin文件中加载,以及dump到json和bin文件中
|
||||
* 实现可视化格式和二进制格式的相互转换。
|
||||
* <p>
|
||||
* 我们导入和更新数据来自于可视化json,他易于编辑,但是文本文件提及太大。
|
||||
* 线上使用二进制格式,能快速加载和解析,但是他没办法编辑和直接查看
|
||||
*/
|
||||
public interface ILoader {
|
||||
/**
|
||||
* 从一个class path路径加载离线数据
|
||||
*
|
||||
* @return 数据
|
||||
*/
|
||||
GeoData load();
|
||||
|
||||
/**
|
||||
* 将离线数据dump到文件中,并且这个文件可以被load再次识别
|
||||
*
|
||||
* @param path 文件
|
||||
* @param geoData 数据结构对象
|
||||
*/
|
||||
void dump(File path, GeoData geoData);
|
||||
|
||||
class Storage {
|
||||
private static GeoData geoData = null;
|
||||
@Setter
|
||||
private static ILoader loader = new BinLoader();
|
||||
|
||||
public static void clear() {
|
||||
geoData = null;
|
||||
}
|
||||
|
||||
public static GeoData getOrLoad() {
|
||||
if (geoData != null) {
|
||||
return geoData;
|
||||
}
|
||||
synchronized (Storage.class) {
|
||||
if (geoData != null) {
|
||||
return geoData;
|
||||
}
|
||||
geoData = loader.load();
|
||||
}
|
||||
return geoData;
|
||||
}
|
||||
}
|
||||
}
|
79
src/main/java/com/dengxq/lnglat2Geo/loader/JSONLoader.java
Normal file
79
src/main/java/com/dengxq/lnglat2Geo/loader/JSONLoader.java
Normal file
@ -0,0 +1,79 @@
|
||||
package com.dengxq.lnglat2Geo.loader;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.parser.ParserConfig;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.dengxq.lnglat2Geo.entity.AdminBoundary;
|
||||
import com.dengxq.lnglat2Geo.entity.AdminNode;
|
||||
import com.dengxq.lnglat2Geo.entity.BusinessAreaGroup;
|
||||
import com.dengxq.lnglat2Geo.entity.CellAdmin;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 从json资源中加载数据,生产不会使用它
|
||||
*/
|
||||
public class JSONLoader implements ILoader {
|
||||
|
||||
private static String loadResource(String key) {
|
||||
key = "json/" + key;
|
||||
try (InputStream inputStream = JSONLoader.class.getClassLoader().getResourceAsStream(key)) {
|
||||
if (inputStream == null) {
|
||||
throw new RuntimeException("resource not exist: " + key);
|
||||
}
|
||||
return IOUtils.toString(inputStream);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String FILE_ADMIN_DATA = "adminData.json";
|
||||
private static final String FILE_STREET_DATA = "streetData.json";
|
||||
private static final String FILE_CELL_ADMINS = "cellAdmins.json";
|
||||
private static final String FILE_ADMIN_BOUNDARIES = "adminBoundaries.json";
|
||||
private static final String FILE_AREA_GROUPS = "areaGroups.json";
|
||||
private static final String FILE_CITY_LEVEL_DATA = "cityLevelData.json";
|
||||
private static final String FILE_COUNTRY_CODE_DATA = "countryCode.json";
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public GeoData load() {
|
||||
GeoData geoData = new GeoData();
|
||||
geoData.setAdminData(JSON.parseArray(loadResource(FILE_ADMIN_DATA), AdminNode.class));
|
||||
geoData.setStreetData(JSON.parseArray(loadResource(FILE_STREET_DATA), AdminNode.class));
|
||||
|
||||
geoData.setAdminBoundaries(JSON.parseArray(loadResource(FILE_ADMIN_BOUNDARIES), AdminBoundary.class));
|
||||
geoData.setAreaGroups(JSON.parseArray(loadResource(FILE_AREA_GROUPS), BusinessAreaGroup.class));
|
||||
geoData.setCellAdmins(JSON.parseArray(loadResource(FILE_CELL_ADMINS), CellAdmin.class));
|
||||
|
||||
geoData.setCityLevel(JSON.parseObject(loadResource(FILE_CITY_LEVEL_DATA), Map.class));
|
||||
geoData.setCountryCode(JSON.parseObject(loadResource(FILE_COUNTRY_CODE_DATA), Map.class));
|
||||
return geoData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(File path, GeoData geoData) {
|
||||
try {
|
||||
// 在测试环境,我们大概率不希望所有数据都存储在一个大json中,所以这里我们分割多个文件存储
|
||||
// 二进制不具备可读性,故合并所有数据在同一个文件
|
||||
dumpObject(new File(path, FILE_ADMIN_DATA), geoData.getAdminData());
|
||||
dumpObject(new File(path, FILE_STREET_DATA), geoData.getStreetData());
|
||||
dumpObject(new File(path, FILE_ADMIN_BOUNDARIES), geoData.getAdminBoundaries());
|
||||
dumpObject(new File(path, FILE_AREA_GROUPS), geoData.getAreaGroups());
|
||||
dumpObject(new File(path, FILE_CELL_ADMINS), geoData.getCellAdmins());
|
||||
dumpObject(new File(path, FILE_CITY_LEVEL_DATA), geoData.getCityLevel());
|
||||
dumpObject(new File(path, FILE_COUNTRY_CODE_DATA), geoData.getCountryCode());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpObject(File file, Object obj) throws IOException {
|
||||
FileUtils.writeStringToFile(file, JSON.toJSONString(obj, SerializerFeature.PrettyFormat));
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
217
src/main/java/com/dengxq/lnglat2Geo/utils/AdminUtils.java
Normal file
217
src/main/java/com/dengxq/lnglat2Geo/utils/AdminUtils.java
Normal file
@ -0,0 +1,217 @@
|
||||
package com.dengxq.lnglat2Geo.utils;
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.enums.DistrictLevel;
|
||||
import com.speedment.common.tuple.Tuple2;
|
||||
import com.speedment.common.tuple.Tuples;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AdminUtils {
|
||||
public static final String[] NATIONS = "阿昌族,鄂温克族,傈僳族,水族,白族,高山族,珞巴族,塔吉克族,保安族,仡佬族,满族,塔塔尔族,布朗族,哈尼族,毛南族,土家族,布依族,哈萨克族,门巴族,土族,朝鲜族,汉族,蒙古族,佤族,达斡尔族,赫哲族,苗族,维吾尔族,傣族,回族,仫佬族,乌孜别克族,德昂族,基诺族,纳西族,锡伯族,东乡族,京族,怒族,瑶族,侗族,景颇族,普米族,彝族,独龙族,柯尔克孜族,羌族,裕固族,俄罗斯族,拉祜族,撒拉族,藏族,鄂伦春族,黎族,畲族,壮族".split(",");
|
||||
|
||||
public static final Pattern p1 = Pattern.compile("(.+)(?:省|市)$");
|
||||
public static final Pattern p2 = Pattern.compile("(.+)自治区");
|
||||
public static final Pattern p3 = Pattern.compile("(.+)特别行政区");
|
||||
|
||||
public static final Pattern c0 = Pattern.compile("^(.{2})$"); // 2 长度为2的 "东区" "南区"
|
||||
public static final Pattern c1 = Pattern.compile("(.+)(?:自治州|自治县)$"); // 30 自治州 琼中黎族苗族自治县
|
||||
public static final Pattern c2 = Pattern.compile("(.+)[市|盟|州]$"); // 304 地级市, 盟; + 1恩施州
|
||||
public static final Pattern c3 = Pattern.compile("(.+)地区$"); // 8 地区
|
||||
public static final Pattern c4 = Pattern.compile("(.+)(?:群岛|填海区)$"); // 2 东沙群岛
|
||||
public static final Pattern c5 = Pattern.compile("(.+[^地郊城堂])区$"); // 20 港澳 不含"东区" "南区"2个字的
|
||||
public static final Pattern c6 = Pattern.compile("(.+)(?:城区|郊县)$"); // 6 九龙城区,上海城区,天津城区,北京城区,重庆城区,重庆郊县
|
||||
public static final Pattern c7 = Pattern.compile("(.+[^郊])县$"); // 12 台湾的xx县
|
||||
|
||||
public static final Pattern d0 = Pattern.compile("^(.{2})$"); // 2 长度为2的 "随县"
|
||||
public static final Pattern d1 = Pattern.compile("(.+)[市]$"); // 304 城区 “赤水市”
|
||||
public static final Pattern d2 = Pattern.compile("(.+)自治县$"); // 30 自治县
|
||||
public static final Pattern d3 = Pattern.compile("(.+)自治州直辖$"); // 30 自治州直辖 "海西蒙古族藏族自治州直辖"
|
||||
public static final Pattern d4 = Pattern.compile("(.+)[区|县]$"); // 8 区县
|
||||
public static final Pattern d5 = Pattern.compile("(.+)(?:乡|镇|街道)$"); // 8 乡镇|街道
|
||||
|
||||
public static final Pattern s0 = Pattern.compile("^(.{2})$");
|
||||
public static final Pattern s1 = Pattern.compile("(.+)(?:特别行政管理区|街道办事处|旅游经济特区|民族乡|地区街道)$");
|
||||
public static final Pattern s2 = Pattern.compile("(.+)(?:镇|乡|村|街道|苏木|老街|管理区|区公所|苏木|办事处|社区|经济特区|行政管理区)$");
|
||||
|
||||
|
||||
public static String shortProvince(String province) {
|
||||
Matcher matcher = p1.matcher(province);
|
||||
if (matcher.matches()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
|
||||
matcher = p2.matcher(province);
|
||||
if (matcher.matches()) {
|
||||
String x = matcher.group(1);
|
||||
if (x.equals("内蒙古")) {
|
||||
return x;
|
||||
}
|
||||
return replaceNations(x);
|
||||
}
|
||||
|
||||
matcher = p3.matcher(province);
|
||||
if (matcher.matches()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
|
||||
return province;
|
||||
}
|
||||
|
||||
|
||||
public static Tuple2<String, Integer> shortCityImp(String city) {
|
||||
// 总数 383
|
||||
Matcher matcher = c0.matcher(city);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 0);
|
||||
}
|
||||
matcher = c1.matcher(city);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(replaceNations(matcher.group(1)), 2);
|
||||
}
|
||||
matcher = c2.matcher(city);
|
||||
if (matcher.matches()) {
|
||||
String x = matcher.group(1);
|
||||
if (x.equals("襄樊")) {
|
||||
x = "襄阳";
|
||||
}
|
||||
return Tuples.of(x, 1);
|
||||
}
|
||||
matcher = c3.matcher(city);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 3);
|
||||
}
|
||||
matcher = c4.matcher(city);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 4);
|
||||
}
|
||||
matcher = c5.matcher(city);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 5);
|
||||
}
|
||||
matcher = c6.matcher(city);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 6);
|
||||
}
|
||||
matcher = c7.matcher(city);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 7);
|
||||
}
|
||||
return Tuples.of(city, -1);
|
||||
}
|
||||
|
||||
|
||||
public static Tuple2<String, Integer> shortDistrictImp(String district) {
|
||||
// 总数 2963 56个内蒙八旗和新疆兵团没有处理
|
||||
Matcher matcher = d0.matcher(district);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 0);
|
||||
}
|
||||
matcher = d1.matcher(district);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 1);
|
||||
}
|
||||
matcher = d2.matcher(district);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(replaceNations(matcher.group(1)), 2);
|
||||
}
|
||||
matcher = d3.matcher(district);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(replaceNations(matcher.group(1)), 3);
|
||||
}
|
||||
matcher = d4.matcher(district);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 4);
|
||||
}
|
||||
matcher = d5.matcher(district);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 5);
|
||||
}
|
||||
return Tuples.of(district, -1);
|
||||
}
|
||||
|
||||
|
||||
public static Tuple2<String, Integer> shortStreetImp(String street) {
|
||||
// 总数 42387
|
||||
// 柘城县邵园乡人民政府, 保安镇, 鹅湖镇人民政府, 东风地区
|
||||
Matcher matcher = s0.matcher(street);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(matcher.group(1), 0);
|
||||
}
|
||||
|
||||
matcher = s1.matcher(street);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(replaceNationsNotEmpty(matcher.group(1)), 1);
|
||||
}
|
||||
|
||||
|
||||
matcher = s2.matcher(street);
|
||||
if (matcher.matches()) {
|
||||
return Tuples.of(replaceNationsNotEmpty(matcher.group(1)), 1);
|
||||
}
|
||||
|
||||
return Tuples.of(street, -1);
|
||||
}
|
||||
|
||||
|
||||
public static String replaceNations(String ncity) {
|
||||
for (String y : NATIONS) {
|
||||
ncity = ncity.replace(y, "");
|
||||
if (y.length() > 2) {
|
||||
String replace = y.replaceAll("族", "");
|
||||
ncity = ncity.replace(replace, "");
|
||||
}
|
||||
}
|
||||
return ncity;
|
||||
}
|
||||
|
||||
|
||||
public static String replaceNationsNotEmpty(String name) {
|
||||
String x = name;
|
||||
for (String y : NATIONS) {
|
||||
String x2 = x.replace(y, "");
|
||||
if (y.length() > 2) {
|
||||
String replace = y.replaceAll("族", "");
|
||||
x2 = x2.replace(replace, "");
|
||||
}
|
||||
x = x2.isEmpty() ? x : x2;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
public static String shortCity(String city) {
|
||||
return shortCityImp(city).get0();
|
||||
}
|
||||
|
||||
public static String shortDistrict(String district) {
|
||||
return shortDistrictImp(district).get0();
|
||||
}
|
||||
|
||||
public static String shortStreet(String street) {
|
||||
return shortStreetImp(street).get0();
|
||||
}
|
||||
|
||||
// def shortAdmin(name: String, level: DistrictLevel): String = {
|
||||
// level match {
|
||||
// case DistrictLevel.Province => AdminUtils1.shortProvince(name)
|
||||
// case DistrictLevel.City => AdminUtils1.shortCity(name)
|
||||
// case DistrictLevel.District => AdminUtils1.shortDistrict(name)
|
||||
// case DistrictLevel.Street => AdminUtils1.shortStreet(name)
|
||||
// case _ => name
|
||||
// }
|
||||
// }
|
||||
|
||||
//def shortAdmin(name: String, level: DistrictLevel)
|
||||
public static String shortAdmin(String name, DistrictLevel level) {
|
||||
// if (DistrictLevel.Province().equals(level)) {
|
||||
// return shortProvince(name);
|
||||
// } else if (DistrictLevel.City().equals(level)) {
|
||||
// return shortCity(name);
|
||||
// } else if (DistrictLevel.District().equals(level)) {
|
||||
// return shortDistrict(name);
|
||||
// } else if (DistrictLevel.Street().equals(level)) {
|
||||
// return shortStreet(name);
|
||||
// }
|
||||
return name;
|
||||
}
|
||||
}
|
208
src/main/java/com/dengxq/lnglat2Geo/utils/GeoUtils.java
Normal file
208
src/main/java/com/dengxq/lnglat2Geo/utils/GeoUtils.java
Normal file
@ -0,0 +1,208 @@
|
||||
package com.dengxq.lnglat2Geo.utils;
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.enums.Azimuth;
|
||||
import com.dengxq.lnglat2Geo.entity.Bound;
|
||||
import com.dengxq.lnglat2Geo.entity.enums.CoordinateSystem;
|
||||
import com.dengxq.lnglat2Geo.entity.Location;
|
||||
import com.google.common.geometry.S2;
|
||||
import com.google.common.geometry.S2Cap;
|
||||
import com.google.common.geometry.S2LatLng;
|
||||
import com.speedment.common.tuple.Tuple2;
|
||||
import com.speedment.common.tuple.Tuples;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
public class GeoUtils {
|
||||
private static final double x_PI = Math.PI * 3000.0 / 180.0;
|
||||
private static final double EE = 0.00669342162296594323;
|
||||
private static final double A = 6378245.0; // BJZ54坐标系地球长半轴, m
|
||||
public static double EQUATOR_C = 20037508.3427892; // 赤道周长, m
|
||||
public static double EARTH_RADIUS = 6378137.0;//WGS84, CGCS2000坐标系地球长半轴, m
|
||||
public static double EARTH_POLAR_RADIUS = 6356725.0; //极半径, m
|
||||
|
||||
public static double SQRT2 = 1.414213562;
|
||||
|
||||
public static double rad(Double d) {
|
||||
return d * Math.PI / 180.0;
|
||||
}
|
||||
|
||||
public static Double distance(Location locA, Location locB) {
|
||||
Double lngA = locA.lng;
|
||||
Double latA = locA.lat;
|
||||
Double lngB = locB.lng;
|
||||
Double latB = locB.lat;
|
||||
double f = rad((latA + latB) / 2);
|
||||
double g = rad((latA - latB) / 2);
|
||||
double l = rad((lngA - lngB) / 2);
|
||||
if (g == 0 && l == 0)
|
||||
return 0D;
|
||||
double sg = Math.sin(g), sl = Math.sin(l), sf = Math.sin(f);
|
||||
|
||||
double s, c, w, r, d, h1, h2, dis, a = EARTH_RADIUS, fl = 1 / 298.257;
|
||||
sg = sg * sg;
|
||||
sl = sl * sl;
|
||||
sf = sf * sf;
|
||||
s = sg * (1 - sl) + (1 - sf) * sl;
|
||||
c = (1 - sg) * (1 - sl) + sf * sl;
|
||||
w = Math.atan(Math.sqrt(s / c));
|
||||
r = Math.sqrt(s * c) / w;
|
||||
d = 2 * w * a;
|
||||
h1 = (3 * r - 1) / 2 / c;
|
||||
h2 = (3 * r + 1) / 2 / s;
|
||||
dis = d * (1 + fl * (h1 * sf * (1 - sg) - h2 * (1 - sf) * sg));
|
||||
|
||||
//return dis.formatted("%.2f").toDouble
|
||||
return new BigDecimal(dis).setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static Tuple2<Double, Double> gcj02ToWgs84(Double lng, Double lat) {
|
||||
if (outOfChina(lng, lat))
|
||||
return Tuples.of(lng, lat);
|
||||
double dlat = transformLat(lng - 105.0, lat - 35.0);
|
||||
double dlng = transformLng(lng - 105.0, lat - 35.0);
|
||||
double radlat = lat / 180.0 * Math.PI;
|
||||
double magic = Math.sin(radlat);
|
||||
magic = 1 - EE * magic * magic;
|
||||
double sqrtmagic = Math.sqrt(magic);
|
||||
dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * Math.PI);
|
||||
dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * Math.PI);
|
||||
double mglat = lat + dlat;
|
||||
double mglng = lng + dlng;
|
||||
return Tuples.of(lng * 2 - mglng, lat * 2 - mglat);
|
||||
}
|
||||
|
||||
|
||||
public static Tuple2<Double, Double> gcj02ToBD09(Double lng, Double lat) {
|
||||
if (outOfChina(lng, lat)) return Tuples.of(lng, lat);
|
||||
double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);
|
||||
double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);
|
||||
double bd_lng = z * Math.cos(theta) + 0.0065;
|
||||
double bd_lat = z * Math.sin(theta) + 0.006;
|
||||
return Tuples.of(bd_lng, bd_lat);
|
||||
}
|
||||
|
||||
public static Tuple2<Double, Double> bd09ToGCJ02(Double lng, Double lat) {
|
||||
if (outOfChina(lng, lat)) return Tuples.of(lng, lat);
|
||||
double x = lng - 0.0065;
|
||||
double y = lat - 0.006;
|
||||
double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);
|
||||
double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);
|
||||
double gg_lng = z * Math.cos(theta);
|
||||
double gg_lat = z * Math.sin(theta);
|
||||
return Tuples.of(gg_lng, gg_lat);
|
||||
}
|
||||
|
||||
public static Tuple2<Double, Double> wgs84ToGCj02(Double lng, Double lat) {
|
||||
double mglat, mglng;
|
||||
if (outOfChina(lng, lat)) {
|
||||
mglat = lat;
|
||||
mglng = lng;
|
||||
} else {
|
||||
double dLat = transformLat(lng - 105.0, lat - 35.0);
|
||||
double dLon = transformLng(lng - 105.0, lat - 35.0);
|
||||
double radLat = lat / 180.0 * Math.PI;
|
||||
double magic = Math.sin(radLat);
|
||||
magic = 1 - EE * magic * magic;
|
||||
double sqrtMagic = Math.sqrt(magic);
|
||||
dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI);
|
||||
dLon = (dLon * 180.0) / (A / sqrtMagic * Math.cos(radLat) * Math.PI);
|
||||
mglat = lat + dLat;
|
||||
mglng = lng + dLon;
|
||||
}
|
||||
return Tuples.of(mglng, mglat);
|
||||
}
|
||||
|
||||
|
||||
private static Double transformLng(Double lng, Double lat) {
|
||||
double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
|
||||
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
|
||||
ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 * Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0;
|
||||
ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 * Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Double transformLat(Double lng, Double lat) {
|
||||
double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
|
||||
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
|
||||
ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
|
||||
ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 * Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isInChina(Double lng, Double lat) {
|
||||
return !outOfChina(lng, lat);
|
||||
}
|
||||
|
||||
public static boolean outOfChina(Double lng, Double lat) {
|
||||
return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
|
||||
}
|
||||
|
||||
private static final double kEarthCircumferenceMeters = 1000 * 40075.017;
|
||||
|
||||
private static double earthMeters2Radians(Double meters) {
|
||||
return (2 * S2.M_PI) * (meters / kEarthCircumferenceMeters);
|
||||
}
|
||||
|
||||
public static S2Cap genS2Cap(Location loc, Double radius) {
|
||||
S2LatLng s2LatLng = S2LatLng.fromDegrees(loc.lat, loc.lng);
|
||||
double radiusRadians = earthMeters2Radians(radius);
|
||||
return S2Cap.fromAxisHeight(s2LatLng.normalized().toPoint(), (radiusRadians * radiusRadians) / 2);
|
||||
}
|
||||
|
||||
public static Tuple2<Double, Double> toGCJ02(Double lng, Double lat, CoordinateSystem coordType) {
|
||||
switch (coordType) {
|
||||
case WGS84:
|
||||
return GeoUtils.wgs84ToGCj02(lng, lat);
|
||||
case BD09:
|
||||
return GeoUtils.bd09ToGCJ02(lng, lat);
|
||||
default:
|
||||
return Tuples.of(lng, lat);
|
||||
}
|
||||
}
|
||||
|
||||
public static Tuple2<Double, Double> toWGS84(Double lng, Double lat, CoordinateSystem coordType) {
|
||||
switch (coordType) {
|
||||
case GCJ02:
|
||||
return GeoUtils.gcj02ToWgs84(lng, lat);
|
||||
case BD09:
|
||||
Tuple2<Double, Double> d02 = GeoUtils.bd09ToGCJ02(lng, lat);
|
||||
return GeoUtils.gcj02ToWgs84(d02.get0(), d02.get1());
|
||||
default:
|
||||
return Tuples.of(lng, lat);
|
||||
}
|
||||
}
|
||||
|
||||
public static Location move(Location loc, Double distance, Azimuth azimuth) {
|
||||
double radLat = GeoUtils.rad(loc.lat);
|
||||
double radLng = GeoUtils.rad(loc.lng);
|
||||
|
||||
double ec = GeoUtils.EARTH_POLAR_RADIUS + (GeoUtils.EARTH_RADIUS - GeoUtils.EARTH_POLAR_RADIUS) * (90 - loc.lng) / 90;
|
||||
double ed = ec * Math.cos(radLat);
|
||||
|
||||
double dx = distance * Math.sin(azimuth.getCode() * Math.PI / 180);
|
||||
double dy = distance * Math.cos(azimuth.getCode() * Math.PI / 180);
|
||||
|
||||
double lng = (dx / ed + radLng) * 180 / Math.PI;
|
||||
double lat = (dy / ec + radLat) * 180 / Math.PI;
|
||||
|
||||
return new Location(lng, lat);
|
||||
}
|
||||
|
||||
public static Bound genCapBound(Location loc, Double radius) {
|
||||
double swDistance = GeoUtils.SQRT2 * radius;
|
||||
Location sw = move(loc, swDistance, Azimuth.SouthWest);
|
||||
Location ne = move(loc, swDistance, Azimuth.NorthEast);
|
||||
return new Bound(sw, ne);
|
||||
}
|
||||
|
||||
public static Bound genCapInnerBound(Location loc, Double radius) {
|
||||
// val swDistance = SQRT2 / 2d * radius
|
||||
Location sw = move(loc, radius, Azimuth.SouthWest);
|
||||
Location ne = move(loc, radius, Azimuth.NorthEast);
|
||||
return new Bound(sw, ne);
|
||||
}
|
||||
}
|
126
src/main/java/com/dengxq/lnglat2Geo/utils/Leb128.java
Normal file
126
src/main/java/com/dengxq/lnglat2Geo/utils/Leb128.java
Normal file
@ -0,0 +1,126 @@
|
||||
package com.dengxq.lnglat2Geo.utils;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3
|
||||
* section 7.6.
|
||||
* <p>
|
||||
* Leb128编码方式能够显著提升数字类型的空间大小,他的核心原理是我们程序运行的数字大多数在一个很小的范围(大部分在千以内)
|
||||
* 故小数字短编码,大数字长编码。可以使用一个byte存储long类型的数字
|
||||
* <p>
|
||||
* Leb128带来的问题也是数据区域更加不可读,但是这是我们优化存储空间的有效手段
|
||||
* <p>
|
||||
* <p>
|
||||
* 同样是否需要使用Leb128也是参考我们对数据的范围本身的大小参考
|
||||
*/
|
||||
public final class Leb128 {
|
||||
private Leb128() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of bytes in the unsigned LEB128 encoding of the
|
||||
* given value.
|
||||
*
|
||||
* @param value the value in question
|
||||
* @return its write size, in bytes
|
||||
*/
|
||||
public static int unsignedLeb128Size(int value) {
|
||||
// TODO: This could be much cleverer.
|
||||
|
||||
int remaining = value >> 7;
|
||||
int count = 0;
|
||||
|
||||
while (remaining != 0) {
|
||||
remaining >>= 7;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an signed integer from {@code in}.
|
||||
*/
|
||||
public static int readSignedLeb128(ByteBuffer in) {
|
||||
int result = 0;
|
||||
int cur;
|
||||
int count = 0;
|
||||
int signBits = -1;
|
||||
|
||||
do {
|
||||
cur = in.get() & 0xff;
|
||||
result |= (cur & 0x7f) << (count * 7);
|
||||
signBits <<= 7;
|
||||
count++;
|
||||
} while (((cur & 0x80) == 0x80) && count < 5);
|
||||
|
||||
if ((cur & 0x80) == 0x80) {
|
||||
throw new RuntimeException("invalid LEB128 sequence");
|
||||
}
|
||||
|
||||
// Sign extend if appropriate
|
||||
if (((signBits >> 1) & result) != 0) {
|
||||
result |= signBits;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned integer from {@code in}.
|
||||
*/
|
||||
public static int readUnsignedLeb128(ByteBuffer in) {
|
||||
int result = 0;
|
||||
int cur;
|
||||
int count = 0;
|
||||
|
||||
do {
|
||||
cur = in.get() & 0xff;
|
||||
result |= (cur & 0x7f) << (count * 7);
|
||||
count++;
|
||||
} while (((cur & 0x80) == 0x80) && count < 5);
|
||||
|
||||
if ((cur & 0x80) == 0x80) {
|
||||
throw new RuntimeException("invalid LEB128 sequence");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes {@code value} as an unsigned integer to {@code out}, starting at
|
||||
* {@code offset}. Returns the number of bytes written.
|
||||
*/
|
||||
public static void writeUnsignedLeb128(ByteBuffer out, int value) {
|
||||
int remaining = value >>> 7;
|
||||
|
||||
while (remaining != 0) {
|
||||
out.put((byte) ((value & 0x7f) | 0x80));
|
||||
value = remaining;
|
||||
remaining >>>= 7;
|
||||
}
|
||||
|
||||
out.put((byte) (value & 0x7f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes {@code value} as a signed integer to {@code out}, starting at
|
||||
* {@code offset}. Returns the number of bytes written.
|
||||
*/
|
||||
public static void writeSignedLeb128(ByteBuffer out, int value) {
|
||||
int remaining = value >> 7;
|
||||
boolean hasMore = true;
|
||||
int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1;
|
||||
|
||||
while (hasMore) {
|
||||
hasMore = (remaining != end)
|
||||
|| ((remaining & 1) != ((value >> 6) & 1));
|
||||
|
||||
out.put((byte) ((value & 0x7f) | (hasMore ? 0x80 : 0)));
|
||||
value = remaining;
|
||||
remaining >>= 7;
|
||||
}
|
||||
}
|
||||
}
|
38
src/main/java/com/dengxq/lnglat2Geo/utils/LineUtils.java
Normal file
38
src/main/java/com/dengxq/lnglat2Geo/utils/LineUtils.java
Normal file
@ -0,0 +1,38 @@
|
||||
package com.dengxq.lnglat2Geo.utils;
|
||||
|
||||
public class LineUtils {
|
||||
public static Double lineDis(Double x1, Double y1, Double x2, Double y2) {
|
||||
return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
|
||||
}
|
||||
|
||||
public static Double pointToLineDis(Double x1, Double y1, Double x2, Double y2, Double x0, Double y0) {
|
||||
double
|
||||
a = lineDis(x1, y1, x2, y2),// 线段的长度
|
||||
b = lineDis(x1, y1, x0, y0),// 点到起点的距离
|
||||
c = lineDis(x2, y2, x0, y0);// 点到终点的距离
|
||||
//点在端点上
|
||||
if (c <= 0.000001 || b <= 0.000001) {
|
||||
return 0D;
|
||||
}
|
||||
//直线距离过短
|
||||
if (a <= 0.000001) {
|
||||
return b;
|
||||
}
|
||||
// 点在起点左侧,距离等于点到起点距离
|
||||
if (c * c >= a * a + b * b) {
|
||||
return b;
|
||||
}
|
||||
//点在终点右侧,距离等于点到终点距离
|
||||
if (b * b >= a * a + c * c) {
|
||||
return c;
|
||||
}
|
||||
//点在起点和终点中间,为垂线距离
|
||||
double k = (y2 - y1) / (x2 - x1);
|
||||
double z = y1 - k * x1;
|
||||
double p = (a + b + c) / 2;
|
||||
// 半周长
|
||||
double s = Math.sqrt(p * (p - a) * (p - b) * (p - c));//海伦公式求面积
|
||||
return 2 * s / a;// 返回点到线的距离(利用三角形面积公式求高)
|
||||
|
||||
}
|
||||
}
|
68
src/main/java/com/dengxq/lnglat2Geo/utils/Md5Util.java
Normal file
68
src/main/java/com/dengxq/lnglat2Geo/utils/Md5Util.java
Normal file
@ -0,0 +1,68 @@
|
||||
package com.dengxq.lnglat2Geo.utils;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class Md5Util {
|
||||
private static final char[] hexChar = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
|
||||
public static String md5(String input) {
|
||||
return getHashWithInputStream(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
public static String getHashWithInputStream(InputStream inputStream) {
|
||||
try {
|
||||
byte[] buffer = new byte[1024];
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
|
||||
int numRead;
|
||||
while ((numRead = inputStream.read(buffer)) > 0) {
|
||||
md5.update(buffer, 0, numRead);
|
||||
}
|
||||
|
||||
inputStream.close();
|
||||
return toHexString(md5.digest());
|
||||
} catch (Exception var4) {
|
||||
throw new IllegalStateException(var4);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toHexString(byte[] b) {
|
||||
StringBuilder sb = new StringBuilder(b.length * 2);
|
||||
|
||||
for (byte b1 : b) {
|
||||
sb.append(hexChar[(b1 & 240) >>> 4]);
|
||||
sb.append(hexChar[b1 & 15]);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static byte[] hexToByteArray(String inHex) {
|
||||
int hexlen = inHex.length();
|
||||
byte[] result;
|
||||
if (hexlen % 2 == 1) {
|
||||
//奇数
|
||||
hexlen++;
|
||||
result = new byte[(hexlen / 2)];
|
||||
inHex = "0" + inHex;
|
||||
} else {
|
||||
//偶数
|
||||
result = new byte[(hexlen / 2)];
|
||||
}
|
||||
int j = 0;
|
||||
for (int i = 0; i < hexlen; i += 2) {
|
||||
result[j] = hexToByte(inHex.substring(i, i + 2));
|
||||
j++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte hexToByte(String inHex) {
|
||||
return (byte) Integer.parseInt(inHex, 16);
|
||||
}
|
||||
}
|
69
src/main/java/com/dengxq/lnglat2Geo/utils/S2Utils.java
Normal file
69
src/main/java/com/dengxq/lnglat2Geo/utils/S2Utils.java
Normal file
@ -0,0 +1,69 @@
|
||||
package com.dengxq.lnglat2Geo.utils;
|
||||
|
||||
import com.google.common.geometry.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class S2Utils {
|
||||
|
||||
// // 预算,提升速度
|
||||
public static Map<Integer, Double> capHeightMap = new HashMap<Integer, Double>() {{
|
||||
for (int radius : Arrays.asList(2, 4, 8, 16, 32, 64, 128, 256)) {
|
||||
double rad = earthMeters2Radians(radius * 1000D);
|
||||
put(radius * 1000, rad * rad * 2);
|
||||
}
|
||||
}};
|
||||
|
||||
public static double earthMeters2Radians(Double meters) {
|
||||
return (2 * S2.M_PI) * (meters / 40075017);
|
||||
}
|
||||
|
||||
public static List<Long> getCellId(S2LatLng s2LatLng, int radius, int desLevel) {
|
||||
double capHeight = capHeightMap.getOrDefault(radius, 0d);
|
||||
S2Cap cap = S2Cap.fromAxisHeight(s2LatLng.toPoint(), capHeight);
|
||||
S2RegionCoverer coverer = new S2RegionCoverer();
|
||||
coverer.setMaxLevel(desLevel);
|
||||
coverer.setMinLevel(desLevel);
|
||||
//圆形内的cell会自动做聚合,手动拆分
|
||||
return coverer.getCovering(cap)
|
||||
.cellIds()
|
||||
.stream()
|
||||
.flatMap((Function<S2CellId, Stream<Long>>) s2CellId -> {
|
||||
int cellLevel = getLevel(s2CellId.id());
|
||||
if (cellLevel == desLevel) {
|
||||
return Stream.of(s2CellId.id());
|
||||
} else {
|
||||
return childrenCellId(s2CellId, cellLevel, desLevel).stream().map(S2CellId::id);
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
public static int getLevel(Long inputs) {
|
||||
int n = 0;
|
||||
long input = inputs;
|
||||
while (input % 2 == 0) {
|
||||
input = input / 2;
|
||||
n += 1;
|
||||
}
|
||||
return 30 - n / 2;
|
||||
}
|
||||
|
||||
public static List<S2CellId> childrenCellId(S2CellId s2CellId, int curLevel, int desLevel) {
|
||||
List<S2CellId> list = new ArrayList<>();
|
||||
if (curLevel < desLevel) {
|
||||
long interval = (s2CellId.childEnd().id() - s2CellId.childBegin().id()) / 4;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
long id = s2CellId.childBegin().id() + interval * i;
|
||||
S2CellId cellId = new S2CellId(id);
|
||||
list.addAll(childrenCellId(cellId, curLevel + 1, desLevel));
|
||||
}
|
||||
} else {
|
||||
list.add(s2CellId);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
92
src/main/java/com/dengxq/lnglat2Geo/utils/Utils.java
Normal file
92
src/main/java/com/dengxq/lnglat2Geo/utils/Utils.java
Normal file
@ -0,0 +1,92 @@
|
||||
package com.dengxq.lnglat2Geo.utils;
|
||||
|
||||
import com.dengxq.lnglat2Geo.GeoTransImpl;
|
||||
import com.dengxq.lnglat2Geo.entity.AdminBoundary;
|
||||
import com.dengxq.lnglat2Geo.entity.CellAdmin;
|
||||
import com.google.common.geometry.S2CellId;
|
||||
import com.speedment.common.tuple.Tuple2;
|
||||
import com.speedment.common.tuple.Tuple3;
|
||||
import com.speedment.common.tuple.Tuple4;
|
||||
import com.speedment.common.tuple.Tuples;
|
||||
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static Map<Long, Integer> buildBoundaryAdminCell(List<CellAdmin> data) {
|
||||
return data.stream().collect(Collectors.toMap(s -> s.cellId, s -> s.adCode));
|
||||
}
|
||||
|
||||
public static Map<Long, List<Long>> buildBoundaryIndex(Map<Long, List<Tuple3<Long, Integer, Integer>>> boundaryData) {
|
||||
Map<Long, List<Tuple2<Long, Long>>> map = boundaryData.keySet().stream().map(s -> Tuples.of(
|
||||
new S2CellId(s).parent(GeoTransImpl.min_level).id(), s
|
||||
)).collect(Collectors.groupingBy(Tuple2::get0));
|
||||
Map<Long, List<Long>> result = new HashMap<>();
|
||||
for (Map.Entry<Long, List<Tuple2<Long, Long>>> entry : map.entrySet()) {
|
||||
result.put(entry.getKey(), entry.getValue().stream().map(Tuple2::get1).collect(Collectors.toList()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Map<Long, List<Tuple3<Long, Integer, Integer>>> parseBoundaryData(List<AdminBoundary> adminBoundaryInJavas) {
|
||||
Map<Long, List<Tuple4<Long, Long, Integer, Boolean>>> map = adminBoundaryInJavas
|
||||
.stream()
|
||||
.flatMap((Function<AdminBoundary, Stream<Tuple4<Long, Long, Integer, Boolean>>>)
|
||||
adminBoundary -> adminBoundary.boundary.stream().flatMap(
|
||||
(Function<List<Long>, Stream<Tuple4<Long, Long, Integer, Boolean>>>)
|
||||
input -> {
|
||||
List<Tuple4<Long, Long, Integer, Boolean>> ret = new ArrayList<>();
|
||||
for (int i = 0; i < input.size() - 2; i++) {
|
||||
ret.add(Tuples.of(input.get(i), input.get(i + 1), adminBoundary.code, true));
|
||||
ret.add(Tuples.of(input.get(i + 1), input.get(i), adminBoundary.code, false));
|
||||
}
|
||||
return ret.stream();
|
||||
}
|
||||
))
|
||||
.collect(Collectors.groupingBy(Tuple4::get0));
|
||||
|
||||
Map<Long, List<Tuple3<Long, Integer, Integer>>> ret = new HashMap<>();
|
||||
|
||||
for (Map.Entry<Long, List<Tuple4<Long, Long, Integer, Boolean>>> entry : map.entrySet()) {
|
||||
List<Tuple4<Long, Long, Integer, Boolean>> value = entry.getValue();
|
||||
|
||||
Map<Long, List<Tuple3<Long, Integer, Boolean>>> sssMap = value.stream().map(longLongIntegerBooleanTuple4 -> Tuples.of(longLongIntegerBooleanTuple4.get1(), longLongIntegerBooleanTuple4.get2(), longLongIntegerBooleanTuple4.get3())).collect(Collectors.groupingBy(new Function<Tuple3<Long, Integer, Boolean>, Long>() {
|
||||
@Override
|
||||
public Long apply(Tuple3<Long, Integer, Boolean> longIntegerBooleanTuple3) {
|
||||
return longIntegerBooleanTuple3.get0();
|
||||
}
|
||||
}));
|
||||
|
||||
List<Tuple3<Long, Integer, Integer>> ret1 = new ArrayList<>();
|
||||
ret.put(entry.getKey(), ret1);
|
||||
for (Map.Entry<Long, List<Tuple3<Long, Integer, Boolean>>> sss : sssMap.entrySet()) {
|
||||
List<Tuple2<Integer, Boolean>> list = sss.getValue().stream().map(longIntegerBooleanTuple3 ->
|
||||
Tuples.of(longIntegerBooleanTuple3.get1(), longIntegerBooleanTuple3.get2())).sorted((o1, o2) -> o2.get1().compareTo(o1.get1())).collect(Collectors.toList());
|
||||
|
||||
if (list.size() > 2) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (list.size() == 2) {
|
||||
if (!list.get(0).get1() || list.get(1).get1())
|
||||
throw new RuntimeException();
|
||||
Integer first = list.get(0).get0();
|
||||
Integer second = list.get(1).get0();
|
||||
ret1.add(Tuples.of(sss.getKey(), first, second));
|
||||
} else {
|
||||
if (list.get(0).get1()) {
|
||||
ret1.add(Tuples.of(sss.getKey(), list.get(0).get0(), -1));
|
||||
} else {
|
||||
ret1.add(Tuples.of(sss.getKey(), -1, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,389 +0,0 @@
|
||||
北京城区,110100,一线城市
|
||||
安顺市,520400,四线城市
|
||||
毕节市,520500,四线城市
|
||||
贵阳市,520100,二线城市
|
||||
六盘水市,520200,五线城市
|
||||
黔西南布依族苗族自治州,522300,五线城市
|
||||
黔南布依族苗族自治州,522700,四线城市
|
||||
黔东南苗族侗族自治州,522600,四线城市
|
||||
铜仁市,520600,四线城市
|
||||
遵义市,520300,三线城市
|
||||
福州市,350100,二线城市
|
||||
龙岩市,350800,四线城市
|
||||
南平市,350700,四线城市
|
||||
宁德市,350900,三线城市
|
||||
厦门市,350200,二线城市
|
||||
莆田市,350300,三线城市
|
||||
三明市,350400,四线城市
|
||||
泉州市,350500,二线城市
|
||||
漳州市,350600,三线城市
|
||||
重庆城区,500100,新一线城市
|
||||
重庆郊县,500200,未知
|
||||
儋州市,460400,五线城市
|
||||
海口市,460100,三线城市
|
||||
三沙市,460300,五线城市
|
||||
三亚市,460200,三线城市
|
||||
白沙黎族自治县,469025,未知
|
||||
保亭黎族苗族自治县,469029,未知
|
||||
昌江黎族自治县,469026,未知
|
||||
澄迈县,469023,未知
|
||||
定安县,469021,未知
|
||||
东方市,469007,未知
|
||||
乐东黎族自治县,469027,未知
|
||||
临高县,469024,未知
|
||||
陵水黎族自治县,469028,未知
|
||||
琼海市,469002,未知
|
||||
琼中黎族苗族自治县,469030,未知
|
||||
屯昌县,469022,未知
|
||||
万宁市,469006,未知
|
||||
文昌市,469005,未知
|
||||
五指山市,469001,未知
|
||||
中山市,442000,二线城市
|
||||
东沙群岛,442100,未知
|
||||
潮州市,445100,三线城市
|
||||
东莞市,441900,新一线城市
|
||||
佛山市,440600,二线城市
|
||||
广州市,440100,一线城市
|
||||
河源市,441600,四线城市
|
||||
惠州市,441300,二线城市
|
||||
江门市,440700,三线城市
|
||||
揭阳市,445200,三线城市
|
||||
茂名市,440900,四线城市
|
||||
梅州市,441400,四线城市
|
||||
清远市,441800,四线城市
|
||||
汕头市,440500,三线城市
|
||||
汕尾市,441500,四线城市
|
||||
韶关市,440200,四线城市
|
||||
深圳市,440300,一线城市
|
||||
阳江市,441700,四线城市
|
||||
云浮市,445300,五线城市
|
||||
湛江市,440800,三线城市
|
||||
肇庆市,441200,三线城市
|
||||
珠海市,440400,三线城市
|
||||
北海市,450500,四线城市
|
||||
百色市,451000,五线城市
|
||||
崇左市,451400,五线城市
|
||||
防城港市,450600,五线城市
|
||||
桂林市,450300,三线城市
|
||||
贵港市,450800,五线城市
|
||||
河池市,451200,五线城市
|
||||
贺州市,451100,五线城市
|
||||
来宾市,451300,五线城市
|
||||
柳州市,450200,三线城市
|
||||
南宁市,450100,二线城市
|
||||
钦州市,450700,五线城市
|
||||
梧州市,450400,五线城市
|
||||
玉林市,450900,四线城市
|
||||
大同市,140200,四线城市
|
||||
晋城市,140500,五线城市
|
||||
晋中市,140700,四线城市
|
||||
临汾市,141000,四线城市
|
||||
吕梁市,141100,五线城市
|
||||
朔州市,140600,五线城市
|
||||
太原市,140100,二线城市
|
||||
阳泉市,140300,五线城市
|
||||
忻州市,140900,五线城市
|
||||
运城市,140800,四线城市
|
||||
长治市,140400,五线城市
|
||||
白银市,620400,五线城市
|
||||
甘南藏族自治州,623000,五线城市
|
||||
定西市,621100,五线城市
|
||||
嘉峪关市,620200,五线城市
|
||||
金昌市,620300,五线城市
|
||||
酒泉市,620900,五线城市
|
||||
兰州市,620100,二线城市
|
||||
临夏回族自治州,622900,五线城市
|
||||
陇南市,621200,五线城市
|
||||
平凉市,620800,五线城市
|
||||
庆阳市,621000,五线城市
|
||||
天水市,620500,五线城市
|
||||
武威市,620600,五线城市
|
||||
张掖市,620700,五线城市
|
||||
新竹市,710019,未知
|
||||
澎湖县,710010,未知
|
||||
南投县,710008,未知
|
||||
台中市,710016,未知
|
||||
台北市,710009,未知
|
||||
台南市,710014,未知
|
||||
台东县,710013,未知
|
||||
嘉义市,710005,未知
|
||||
嘉义县,710006,未知
|
||||
基隆市,710004,未知
|
||||
屏东县,710011,未知
|
||||
彰化县,710023,未知
|
||||
新北市,710012,未知
|
||||
新竹县,710020,未知
|
||||
桃园县,710018,未知
|
||||
花莲县,710003,未知
|
||||
苗栗县,710007,未知
|
||||
云林县,710022,未知
|
||||
高雄市,710001,未知
|
||||
宜兰县,710021,未知
|
||||
常州市,320400,二线城市
|
||||
淮安市,320800,三线城市
|
||||
连云港市,320700,三线城市
|
||||
南京市,320100,新一线城市
|
||||
南通市,320600,二线城市
|
||||
苏州市,320500,新一线城市
|
||||
宿迁市,321300,三线城市
|
||||
泰州市,321200,三线城市
|
||||
无锡市,320200,新一线城市
|
||||
徐州市,320300,二线城市
|
||||
盐城市,320900,三线城市
|
||||
扬州市,321000,三线城市
|
||||
镇江市,321100,三线城市
|
||||
安阳市,410500,四线城市
|
||||
鹤壁市,410600,五线城市
|
||||
焦作市,410800,四线城市
|
||||
开封市,410200,四线城市
|
||||
漯河市,411100,四线城市
|
||||
洛阳市,410300,三线城市
|
||||
南阳市,411300,三线城市
|
||||
平顶山市,410400,四线城市
|
||||
濮阳市,410900,四线城市
|
||||
商丘市,411400,三线城市
|
||||
三门峡市,411200,五线城市
|
||||
许昌市,411000,三线城市
|
||||
新乡市,410700,三线城市
|
||||
信阳市,411500,三线城市
|
||||
郑州市,410100,新一线城市
|
||||
周口市,411600,四线城市
|
||||
驻马店市,411700,三线城市
|
||||
济源市,419001,未知
|
||||
保定市,130600,二线城市
|
||||
沧州市,130900,三线城市
|
||||
承德市,130800,四线城市
|
||||
邯郸市,130400,三线城市
|
||||
衡水市,131100,四线城市
|
||||
廊坊市,131000,三线城市
|
||||
秦皇岛市,130300,三线城市
|
||||
石家庄市,130100,二线城市
|
||||
唐山市,130200,三线城市
|
||||
邢台市,130500,三线城市
|
||||
张家口市,130700,四线城市
|
||||
安庆市,340800,四线城市
|
||||
蚌埠市,340300,三线城市
|
||||
亳州市,341600,四线城市
|
||||
滁州市,341100,三线城市
|
||||
池州市,341700,五线城市
|
||||
阜阳市,341200,三线城市
|
||||
合肥市,340100,二线城市
|
||||
黄山市,341000,四线城市
|
||||
淮南市,340400,四线城市
|
||||
淮北市,340600,五线城市
|
||||
六安市,341500,四线城市
|
||||
马鞍山市,340500,四线城市
|
||||
铜陵市,340700,三线城市
|
||||
宿州市,341300,四线城市
|
||||
芜湖市,340200,三线城市
|
||||
宣城市,341800,四线城市
|
||||
常德市,430700,三线城市
|
||||
郴州市,431000,四线城市
|
||||
衡阳市,430400,三线城市
|
||||
娄底市,431300,四线城市
|
||||
邵阳市,430500,四线城市
|
||||
湘西土家族苗族自治州,433100,五线城市
|
||||
湘潭市,430300,三线城市
|
||||
永州市,431100,四线城市
|
||||
益阳市,430900,四线城市
|
||||
张家界市,430800,五线城市
|
||||
株洲市,430200,三线城市
|
||||
岳阳市,430600,三线城市
|
||||
长沙市,430100,新一线城市
|
||||
怀化市,431200,四线城市
|
||||
上海城区,310100,一线城市
|
||||
鄂州市,420700,五线城市
|
||||
恩施土家族苗族自治州,422800,五线城市
|
||||
黄石市,420200,四线城市
|
||||
黄冈市,421100,三线城市
|
||||
荆门市,420800,五线城市
|
||||
荆州市,421000,三线城市
|
||||
随州市,421300,五线城市
|
||||
十堰市,420300,四线城市
|
||||
武汉市,420100,新一线城市
|
||||
孝感市,420900,四线城市
|
||||
襄阳市,420600,三线城市
|
||||
咸宁市,421200,四线城市
|
||||
宜昌市,420500,三线城市
|
||||
潜江市,429005,未知
|
||||
神农架林区,429021,未知
|
||||
天门市,429006,未知
|
||||
仙桃市,429004,未知
|
||||
大庆市,230600,三线城市
|
||||
大兴安岭地区,232700,五线城市
|
||||
哈尔滨市,230100,二线城市
|
||||
鹤岗市,230400,五线城市
|
||||
黑河市,231100,五线城市
|
||||
鸡西市,230300,五线城市
|
||||
佳木斯市,230800,四线城市
|
||||
牡丹江市,231000,四线城市
|
||||
齐齐哈尔市,230200,四线城市
|
||||
七台河市,230900,五线城市
|
||||
绥化市,231200,五线城市
|
||||
双鸭山市,230500,五线城市
|
||||
伊春市,230700,五线城市
|
||||
果洛藏族自治州,632600,五线城市
|
||||
海东市,630200,五线城市
|
||||
海北藏族自治州,632200,五线城市
|
||||
海西蒙古族藏族自治州,632800,五线城市
|
||||
海南藏族自治州,632500,五线城市
|
||||
黄南藏族自治州,632300,五线城市
|
||||
西宁市,630100,三线城市
|
||||
玉树藏族自治州,632700,五线城市
|
||||
抚州市,361000,四线城市
|
||||
赣州市,360700,三线城市
|
||||
吉安市,360800,四线城市
|
||||
九江市,360400,三线城市
|
||||
景德镇市,360200,五线城市
|
||||
南昌市,360100,二线城市
|
||||
萍乡市,360300,五线城市
|
||||
上饶市,361100,三线城市
|
||||
新余市,360500,五线城市
|
||||
宜春市,360900,三线城市
|
||||
鹰潭市,360600,五线城市
|
||||
阿拉善盟,152900,五线城市
|
||||
巴彦淖尔市,150800,五线城市
|
||||
包头市,150200,三线城市
|
||||
赤峰市,150400,四线城市
|
||||
鄂尔多斯市,150600,四线城市
|
||||
呼和浩特市,150100,三线城市
|
||||
呼伦贝尔市,150700,四线城市
|
||||
乌海市,150300,五线城市
|
||||
乌兰察布市,150900,五线城市
|
||||
通辽市,150500,四线城市
|
||||
锡林郭勒盟,152500,五线城市
|
||||
兴安盟,152200,五线城市
|
||||
鞍山市,210300,三线城市
|
||||
本溪市,210500,五线城市
|
||||
朝阳市,211300,五线城市
|
||||
丹东市,210600,四线城市
|
||||
大连市,210200,二线城市
|
||||
抚顺市,210400,四线城市
|
||||
阜新市,210900,五线城市
|
||||
葫芦岛市,211400,五线城市
|
||||
锦州市,210700,四线城市
|
||||
辽阳市,211000,五线城市
|
||||
盘锦市,211100,四线城市
|
||||
沈阳市,210100,新一线城市
|
||||
铁岭市,211200,五线城市
|
||||
营口市,210800,四线城市
|
||||
固原市,640400,五线城市
|
||||
石嘴山市,640200,五线城市
|
||||
吴忠市,640300,五线城市
|
||||
银川市,640100,三线城市
|
||||
中卫市,640500,五线城市
|
||||
白城市,220800,五线城市
|
||||
白山市,220600,五线城市
|
||||
吉林市,220200,三线城市
|
||||
辽源市,220400,五线城市
|
||||
松原市,220700,四线城市
|
||||
四平市,220300,五线城市
|
||||
通化市,220500,四线城市
|
||||
延边朝鲜族自治州,222400,四线城市
|
||||
长春市,220100,二线城市
|
||||
滨州市,371600,四线城市
|
||||
德州市,371400,四线城市
|
||||
东营市,370500,四线城市
|
||||
菏泽市,371700,三线城市
|
||||
济南市,370100,二线城市
|
||||
济宁市,370800,三线城市
|
||||
莱芜市,371200,五线城市
|
||||
聊城市,371500,四线城市
|
||||
临沂市,371300,三线城市
|
||||
青岛市,370200,新一线城市
|
||||
日照市,371100,四线城市
|
||||
泰安市,370900,四线城市
|
||||
威海市,371000,三线城市
|
||||
潍坊市,370700,二线城市
|
||||
烟台市,370600,二线城市
|
||||
枣庄市,370400,四线城市
|
||||
淄博市,370300,三线城市
|
||||
安康市,610900,五线城市
|
||||
宝鸡市,610300,四线城市
|
||||
汉中市,610700,五线城市
|
||||
商洛市,611000,五线城市
|
||||
铜川市,610200,五线城市
|
||||
渭南市,610500,四线城市
|
||||
西安市,610100,新一线城市
|
||||
咸阳市,610400,三线城市
|
||||
延安市,610600,五线城市
|
||||
榆林市,610800,四线城市
|
||||
阿克苏地区,652900,五线城市
|
||||
阿勒泰地区,654300,五线城市
|
||||
昌吉回族自治州,652300,五线城市
|
||||
巴音郭楞蒙古自治州,652800,五线城市
|
||||
博尔塔拉蒙古自治州,652700,五线城市
|
||||
哈密市,650500,五线城市
|
||||
和田地区,653200,五线城市
|
||||
克孜勒苏柯尔克孜自治州,653000,五线城市
|
||||
克拉玛依市,650200,五线城市
|
||||
喀什地区,653100,五线城市
|
||||
塔城地区,654200,五线城市
|
||||
乌鲁木齐市,650100,二线城市
|
||||
吐鲁番市,650400,五线城市
|
||||
伊犁哈萨克自治州,654000,五线城市
|
||||
阿拉尔市,659002,未知
|
||||
北屯市,659005,未知
|
||||
可克达拉市,659008,未知
|
||||
昆玉市,659009,未知
|
||||
石河子市,659001,未知
|
||||
双河市,659007,未知
|
||||
铁门关市,659006,未知
|
||||
图木舒克市,659003,未知
|
||||
五家渠市,659004,未知
|
||||
天津城区,120100,新一线城市
|
||||
阿坝藏族羌族自治州,513200,五线城市
|
||||
巴中市,511900,五线城市
|
||||
成都市,510100,新一线城市
|
||||
德阳市,510600,三线城市
|
||||
达州市,511700,五线城市
|
||||
广元市,510800,五线城市
|
||||
广安市,511600,五线城市
|
||||
甘孜藏族自治州,513300,五线城市
|
||||
凉山彝族自治州,513400,五线城市
|
||||
乐山市,511100,四线城市
|
||||
绵阳市,510700,三线城市
|
||||
眉山市,511400,四线城市
|
||||
泸州市,510500,四线城市
|
||||
南充市,511300,四线城市
|
||||
攀枝花市,510400,五线城市
|
||||
内江市,511000,四线城市
|
||||
遂宁市,510900,四线城市
|
||||
雅安市,511800,五线城市
|
||||
宜宾市,511500,四线城市
|
||||
资阳市,512000,五线城市
|
||||
自贡市,510300,五线城市
|
||||
阿里地区,542500,五线城市
|
||||
昌都市,540300,五线城市
|
||||
拉萨市,540100,五线城市
|
||||
林芝市,540400,五线城市
|
||||
那曲地区,542400,五线城市
|
||||
山南市,540500,五线城市
|
||||
日喀则市,540200,五线城市
|
||||
保山市,530500,五线城市
|
||||
楚雄彝族自治州,532300,五线城市
|
||||
大理白族自治州,532900,四线城市
|
||||
德宏傣族景颇族自治州,533100,五线城市
|
||||
迪庆藏族自治州,533400,五线城市
|
||||
红河哈尼族彝族自治州,532500,四线城市
|
||||
昆明市,530100,二线城市
|
||||
丽江市,530700,四线城市
|
||||
临沧市,530900,五线城市
|
||||
怒江傈僳族自治州,533300,五线城市
|
||||
曲靖市,530300,三线城市
|
||||
普洱市,530800,五线城市
|
||||
文山壮族苗族自治州,532600,五线城市
|
||||
西双版纳傣族自治州,532800,五线城市
|
||||
玉溪市,530400,四线城市
|
||||
昭通市,530600,五线城市
|
||||
杭州市,330100,新一线城市
|
||||
嘉兴市,330400,二线城市
|
||||
湖州市,330500,三线城市
|
||||
丽水市,331100,四线城市
|
||||
金华市,330700,二线城市
|
||||
衢州市,330800,四线城市
|
||||
宁波市,330200,新一线城市
|
||||
绍兴市,330600,二线城市
|
||||
台州市,331000,二线城市
|
||||
温州市,330300,二线城市
|
||||
舟山市,330900,四线城市
|
@ -1,189 +0,0 @@
|
||||
YU,南斯拉夫
|
||||
NL,荷兰
|
||||
LT,立陶宛
|
||||
TN,突尼斯
|
||||
TJ,塔吉克斯坦
|
||||
MY,马来西亚
|
||||
AG,安提瓜和巴布达
|
||||
GM,冈比亚
|
||||
AU,澳大利亚
|
||||
WH,开曼群岛
|
||||
JO,约旦
|
||||
QA,卡塔尔
|
||||
PY,巴拉圭
|
||||
UY,乌拉圭
|
||||
DE,德国
|
||||
IN,印度
|
||||
KP,朝鲜
|
||||
YE,也门
|
||||
MN,蒙古
|
||||
NI,尼加拉瓜
|
||||
LS,莱索托
|
||||
CL,智利
|
||||
KW,科威特
|
||||
SK,斯洛伐克
|
||||
WD,马提尼克
|
||||
MC,摩纳哥
|
||||
MT,马耳他
|
||||
DO,多米尼加共和国
|
||||
MX,墨西哥
|
||||
BF,布基纳法索
|
||||
GE,格鲁吉亚
|
||||
MU,毛里求斯
|
||||
SN,塞内加尔
|
||||
BB,巴巴多斯
|
||||
AZ,阿塞拜疆
|
||||
MD,摩尔多瓦
|
||||
AL,阿尔巴尼亚
|
||||
WB,阿森松
|
||||
SO,索马里
|
||||
NG,尼日利亚
|
||||
HT,海地
|
||||
BZ,伯利兹
|
||||
MZ,莫桑比克
|
||||
NP,尼泊尔
|
||||
WG,西萨摩亚
|
||||
EG,埃及
|
||||
VC,圣文森特
|
||||
MW,马拉维
|
||||
ZA,南非
|
||||
BS,巴哈马
|
||||
IQ,伊拉克
|
||||
KH,柬埔寨
|
||||
LY,利比亚
|
||||
PE,秘鲁
|
||||
SM,圣马力诺
|
||||
MS,蒙特塞拉特岛
|
||||
BD,孟加拉国
|
||||
DK,丹麦
|
||||
ZR,扎伊尔
|
||||
WF,东萨摩亚(美)
|
||||
GN,几内亚
|
||||
GH,加纳
|
||||
PK,巴基斯坦
|
||||
DJ,吉布提
|
||||
NA,纳米比亚
|
||||
IT,意大利
|
||||
AT,奥地利
|
||||
FR,法国
|
||||
CU,古巴
|
||||
TZ,坦桑尼亚
|
||||
RU,俄罗斯
|
||||
CG,刚果
|
||||
LV,拉脱维亚
|
||||
MV,马尔代夫
|
||||
SR,苏里南
|
||||
IS,冰岛
|
||||
BI,布隆迪
|
||||
FJ,斐济
|
||||
GR,希腊
|
||||
TD,乍得
|
||||
NE,尼日尔
|
||||
NR,瑙鲁
|
||||
SV,萨尔瓦多
|
||||
CA,加拿大
|
||||
SZ,斯威士兰
|
||||
TR,土耳其
|
||||
TO,汤加
|
||||
BE,比利时
|
||||
CZ,捷克
|
||||
KR,韩国
|
||||
JP,日本
|
||||
CF,中非共和国
|
||||
GB,英国
|
||||
LC,圣卢西亚
|
||||
TT,特立尼达和多巴哥
|
||||
WA,科特迪瓦
|
||||
VE,委内瑞拉
|
||||
EC,厄瓜多尔
|
||||
MG,马达加斯加
|
||||
HU,匈牙利
|
||||
TM,土库曼斯坦
|
||||
RO,罗马尼亚
|
||||
EE,爱沙尼亚
|
||||
OM,阿曼
|
||||
GI,直布罗陀
|
||||
KZ,哈萨克斯坦
|
||||
CR,哥斯达黎加
|
||||
CK,库克群岛
|
||||
JM,牙买加
|
||||
GU,关岛
|
||||
ZM,赞比亚
|
||||
BM,百慕大群岛
|
||||
SA,沙特阿拉伯
|
||||
PL,波兰
|
||||
KE,肯尼亚
|
||||
SL,塞拉利昂
|
||||
PA,巴拿马
|
||||
VC,圣文森特岛
|
||||
UG,乌干达
|
||||
SI,斯洛文尼亚
|
||||
IE,爱尔兰
|
||||
LR,利比里亚
|
||||
CM,喀麦隆
|
||||
GF,法属圭亚那
|
||||
AI,安圭拉岛
|
||||
MM,缅甸
|
||||
AE,阿拉伯联合酋长国
|
||||
BY,白俄罗斯
|
||||
CN,中国
|
||||
LI,列支敦士登
|
||||
VN,越南
|
||||
IL,以色列
|
||||
PH,菲律宾
|
||||
FI,芬兰
|
||||
AF,阿富汗
|
||||
BH,巴林
|
||||
NO,挪威
|
||||
WE,荷属安的列斯
|
||||
SG,新加坡
|
||||
TG,多哥
|
||||
LB,黎巴嫩
|
||||
BG,保加利亚
|
||||
GY,圭亚那
|
||||
SB,所罗门群岛
|
||||
MA,摩洛哥
|
||||
UA,乌克兰
|
||||
UZ,乌兹别克斯坦
|
||||
CO,哥伦比亚
|
||||
KG,吉尔吉斯坦
|
||||
CY,塞浦路斯
|
||||
DZ,阿尔及利亚
|
||||
PR,波多黎各
|
||||
GA,加蓬
|
||||
BJ,贝宁
|
||||
BN,文莱
|
||||
AR,阿根廷
|
||||
LA,老挝
|
||||
ST,圣多美和普林西比
|
||||
WI,马里亚那群岛
|
||||
ZW,津巴布韦
|
||||
LC,圣卢西亚
|
||||
SC,塞舌尔
|
||||
ML,马里
|
||||
IR,伊朗
|
||||
GD,格林纳达
|
||||
ET,埃塞俄比亚
|
||||
LK,斯里兰卡
|
||||
TH,泰国
|
||||
CH,瑞士
|
||||
SY,叙利亚
|
||||
NZ,新西兰
|
||||
ES,西班牙
|
||||
PG,巴布亚新几内亚
|
||||
GT,危地马拉
|
||||
SE,瑞典
|
||||
ID,印度尼西亚
|
||||
AO,安哥拉
|
||||
PF,法属玻利尼西亚
|
||||
PT,葡萄牙
|
||||
BR,巴西
|
||||
AD,安道尔共和国
|
||||
HN,洪都拉斯
|
||||
SD,苏丹
|
||||
LU,卢森堡
|
||||
BW,博茨瓦纳
|
||||
BO,玻利维亚
|
||||
AM,亚美尼亚
|
||||
US,美国
|
||||
WC,留尼旺
|
Binary file not shown.
Binary file not shown.
@ -1,6 +0,0 @@
|
||||
log4j.rootLogger=DEBUG, stdout
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.Target=System.out
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %c{1}:%L %t %r - %m%n
|
||||
|
@ -1,124 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo
|
||||
|
||||
import com.dengxq.lnglat2Geo.build.AdminDataProvider
|
||||
import com.dengxq.lnglat2Geo.entity.CoordinateSystem.CoordinateSystem
|
||||
import com.dengxq.lnglat2Geo.entity.DistrictLevel.DistrictLevel
|
||||
import com.dengxq.lnglat2Geo.entity.{Admin, BusinessArea, BusinessAreaInfo, CoordinateSystem, _}
|
||||
import com.dengxq.lnglat2Geo.utils.GeoUtils
|
||||
import collection.JavaConversions._
|
||||
|
||||
object GeoTrans {
|
||||
|
||||
/**
|
||||
* 按需初始化数据, 离线数据处理可以不加载该方法
|
||||
*
|
||||
* @param needBoundary 是否需要加载边界数据,用于经纬度转换省市区县
|
||||
* @param needStreet 是否需要加载街道数据
|
||||
* @param needArea 是否需要加载商圈数据
|
||||
* @param needCityLevel 是否需要加载城市级别数据
|
||||
* @return
|
||||
*/
|
||||
def init(needBoundary: Boolean = true, needStreet: Boolean = true, needArea: Boolean = false, needCityLevel: Boolean = false): Unit = {
|
||||
GeoTransImpl.init(needBoundary, needArea, needStreet, needCityLevel)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断经纬度的行政区划
|
||||
* @param lon 经度
|
||||
* @param lat 纬度
|
||||
* @param needStreet 是否需要街道信息
|
||||
* @param coordSys 输入经纬度的坐标系
|
||||
* @return 行政区划
|
||||
*/
|
||||
def determineAdmin(lon: Double, lat: Double, coordSys: CoordinateSystem, needStreet: Boolean = true): Admin = {
|
||||
GeoTransImpl.determineAdmin(lon, lat, needStreet, coordSys)
|
||||
}
|
||||
|
||||
/**
|
||||
* 给出附近的所有商圈信息
|
||||
*
|
||||
* @param lon 经度
|
||||
* @param lat 纬度
|
||||
* @param radius 需要商圈的半径
|
||||
* @param coordSys 输入经纬度的坐标系
|
||||
* @param needStreet 是否需要返回行政区划的街道信息
|
||||
* @return
|
||||
*/
|
||||
def aroundBusinessAreas(lon: Double, lat: Double, radius: Int = 4000, coordSys: CoordinateSystem, needStreet: Boolean = true): BusinessAreaInfo = {
|
||||
val gcj02LonLat = GeoUtils.toGCJ02(lon, lat, coordSys)
|
||||
val admin = determineAdmin(gcj02LonLat._1, gcj02LonLat._2, CoordinateSystem.GCJ02, needStreet)
|
||||
GeoTransImpl.determineAreaByAdmin(gcj02LonLat._1, gcj02LonLat._2, admin, radius)
|
||||
}
|
||||
|
||||
/**
|
||||
* 给出附近的所有商圈信息
|
||||
*
|
||||
* @param lon 经度
|
||||
* @param lat 纬度
|
||||
* @param radius 需要商圈的半径
|
||||
* @param coordSys 输入经纬度的坐标系
|
||||
* @param cityID 输入城市adcode
|
||||
* @return
|
||||
*/
|
||||
def aroundBusinessAreasByCityID(lon: Double, lat: Double, radius: Int = 4000, coordSys: CoordinateSystem, cityID: Int): Seq[BusinessArea] = {
|
||||
GeoTransImpl.determineAreaByCityId(lon, lat, cityID, radius, coordSys)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取城市级别
|
||||
* @param adcodeOrName 城市adcode或者城市名
|
||||
* @return 城市级别
|
||||
*/
|
||||
def getCityLevel(adcodeOrName: String): String = {
|
||||
GeoTransImpl.getCityLevel(adcodeOrName)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据地区code返回规范数据
|
||||
* @param adcode 地区code
|
||||
* @return
|
||||
*/
|
||||
def normalizeName(adcode: Int): AdminNode = {
|
||||
GeoTransImpl.normalizeName(adcode)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据地区name返回规范化的地区信息
|
||||
* @param adcode 地区code
|
||||
* @return 规范化的地区信息
|
||||
*/
|
||||
def normalizeName(name: String, level: DistrictLevel, isFullMatch: Boolean): java.util.List[AdminNode] = {
|
||||
GeoTransImpl.normalizeName(name, level, isFullMatch)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据所有信息返回规范化的地区信息
|
||||
*
|
||||
* @param province 省名 可为空
|
||||
* @param city 城市名 可为空
|
||||
* @param district 区县名 可为空
|
||||
* @param street 街道名 可为空
|
||||
* @param isFullMatch 所有输入区域是简称还是全名
|
||||
* @return 规范化的地区信息,可能有多个或不存在
|
||||
*/
|
||||
def normalizeName(province: String = "", city: String = "", district: String = "", street: String = "", isFullMatch: Boolean = false): Seq[Admin] = {
|
||||
GeoTransImpl.normalizeName(province, city, district, street, isFullMatch)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有行政区划数据
|
||||
*
|
||||
* @return 所有行政区划数据,不包含街道
|
||||
*/
|
||||
def adminData() : Map[Int, AdminNode] = {
|
||||
GeoTransImpl.adminData
|
||||
}
|
||||
|
||||
def countryCode() : Map[String, String] = {
|
||||
GeoTransImpl.countryCode
|
||||
}
|
||||
|
||||
def districtBoundary(): Map[Int, Array[Array[Long]]] = {
|
||||
AdminDataProvider.AdminLoader.loadBoundarySrc
|
||||
}
|
||||
}
|
@ -1,269 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo
|
||||
|
||||
import com.dengxq.lnglat2Geo.build.{AdminDataProvider, CityAreaDataProvider, CityLevelDataProvider}
|
||||
import com.dengxq.lnglat2Geo.entity.CoordinateSystem.CoordinateSystem
|
||||
import com.dengxq.lnglat2Geo.entity.DistrictLevel.DistrictLevel
|
||||
import com.dengxq.lnglat2Geo.entity._
|
||||
import com.dengxq.lnglat2Geo.utils.{GeoUtils, LineUtils, S2Utils}
|
||||
import com.google.common.geometry.{S2CellId, S2LatLng}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
private[lnglat2Geo] object GeoTransImpl {
|
||||
|
||||
final val min_level = 12
|
||||
|
||||
lazy val cityLevelData: Map[String, String] = CityLevelDataProvider.csv2Map
|
||||
lazy val cityBusinessArea: Map[Int, Array[BusinessAreaData]] = CityAreaDataProvider.loadBusinessAreaData
|
||||
|
||||
lazy val adminData: Map[Int, AdminNode] = AdminDataProvider.AdminLoader.loadAdminData
|
||||
lazy val streetData: Map[Int, AdminNode] = AdminDataProvider.AdminLoader.loadStreetData.map(s => (s.id, s)).toMap
|
||||
lazy val countryCode: Map[String, String] = AdminDataProvider.AdminLoader.loadCountryCode
|
||||
|
||||
lazy val boundaryData: Map[Long, List[(Long, Int, Int)]] = AdminDataProvider.AdminLoader.loadBoundaryData
|
||||
lazy val boundaryIndex: Map[Long, List[Long]] = boundaryData
|
||||
.keySet
|
||||
.map(s => (new S2CellId(s).parent(min_level).id(), s))
|
||||
.groupBy(_._1)
|
||||
.map(s => (s._1, s._2.map(_._2).toList))
|
||||
lazy val boundaryAdminCell: Map[Long, Int] = AdminDataProvider.AdminLoader.loadBoundaryCellData
|
||||
|
||||
/**
|
||||
* 按需初始化数据
|
||||
*
|
||||
* @param needBoundary 是否需要加载边界数据,用于经纬度转换省市区县
|
||||
* @param needArea 是否需要加载商圈数据
|
||||
* @param needStreet 是否需要加载街道数据
|
||||
* @return
|
||||
*/
|
||||
def init(needBoundary: Boolean = true, needArea: Boolean = false, needStreet: Boolean = true, needCityLevel: Boolean = false): Unit = {
|
||||
adminData
|
||||
if (needBoundary) {
|
||||
boundaryData
|
||||
boundaryIndex
|
||||
boundaryAdminCell
|
||||
}
|
||||
if (needStreet) streetData
|
||||
if (needArea) cityBusinessArea
|
||||
if (needCityLevel) cityLevelData
|
||||
}
|
||||
|
||||
def determineAdmin(lon: Double, lat: Double, needStreet: Boolean = false, coordSys: CoordinateSystem = CoordinateSystem.GCJ02): Admin = {
|
||||
val wgs84LonLat = GeoUtils.toWGS84(lon, lat, coordSys)
|
||||
val code = determineAdminCode(wgs84LonLat._1, wgs84LonLat._2)
|
||||
if (code != -1) {
|
||||
val district = adminData.get(code).orNull
|
||||
val city = if (district.level == DistrictLevel.District) adminData.get(district.parentId).orNull else district
|
||||
val province = if (city.level == DistrictLevel.City) adminData.get(city.parentId).orNull else city
|
||||
|
||||
var streetCode = 0
|
||||
var streetName = ""
|
||||
if (needStreet) {
|
||||
if (district.children.nonEmpty) {
|
||||
val street = district.children.map(s => streetData.get(s))
|
||||
.minBy(s => GeoUtils.distance(s.get.center, Location(wgs84LonLat._1, wgs84LonLat._2))).get
|
||||
streetCode = street.id
|
||||
streetName = street.name
|
||||
}
|
||||
}
|
||||
if (streetCode > 0) Admin.createStreet(province.name, city.name, district.name, streetName, province.id, city.id, district.id, streetCode, district.center)
|
||||
else Admin.createDistrict(province.name, city.name, district.name, province.id, city.id, district.id, district.center)
|
||||
} else Admin.createOversea
|
||||
}
|
||||
|
||||
def getCityLevel(admin: Admin): String = {
|
||||
getCityLevel(admin.cityCode.toString)
|
||||
}
|
||||
|
||||
def getCityLevel(adcode_or_name: String): String = {
|
||||
cityLevelData.getOrElse(adcode_or_name, "未知")
|
||||
}
|
||||
|
||||
def normalizeName(adcode: Int): AdminNode = {
|
||||
adminData.getOrElse(adcode, streetData.getOrElse(adcode, null))
|
||||
}
|
||||
|
||||
def normalizeName(name: String, level: DistrictLevel, isFullMatch: Boolean): List[AdminNode] = {
|
||||
adminData.values.filter(_.level.equals(level))
|
||||
.filter(s => if (isFullMatch) s.name.equals(name) else s.shortName.contains(name) || s.name.contains(name))
|
||||
.toList
|
||||
}
|
||||
|
||||
private val cityNameMap: Map[String, String] = Map(("重庆市", ""), ("上海市", "上海城区"), ("北京市", "北京城区"), ("天津市", "天津城区")
|
||||
,("那曲市", "那曲地区")
|
||||
)
|
||||
|
||||
private val districtNameMap: Map[String, String] = Map(("云州区", "大同县"), ("平城区", "城区"), ("云冈区", "南郊区"),
|
||||
("余江区", "余江县"), ("马龙区", "马龙县"), ("光明区", "宝安区"), ("怀仁区", "怀仁县"), ("彬州市", "彬县"), ("海安市", "海安县"),
|
||||
("漠河市", "漠河县"), ("京山市", "京山县"), ("济阳区", "济阳县"), ("潞州区", "城区"), ("上党区", "长治县"), ("屯留区", "屯留县"), ("潞城区", "潞城市"),
|
||||
("滦州市", "滦县"), ("潜山市", "潜山县"), ("邹平市", "邹平县"), ("荔浦市", "荔浦县"), ("兴仁市", "兴仁县"), ("水富市", "水富县"), ("华亭市", "华亭县"),
|
||||
("积石山县", "积石山保安族东乡族撒拉族自治县"), ("元江县", "元江哈尼族彝族傣族自治县"), ("双江县", "双江拉祜族佤族布朗族傣族自治县"),
|
||||
("孟连县", "孟连傣族拉祜族佤族自治县"), ("镇沅县", "镇沅彝族哈尼族拉祜族自治县"),
|
||||
("大柴旦行政委员会", "海西蒙古族藏族自治州直辖"), ("冷湖行政委员会", "海西蒙古族藏族自治州直辖"), ("茫崖行政委员会", "海西蒙古族藏族自治州直辖"),
|
||||
("上饶县", "广信区"), ("达孜区", "达孜县"),
|
||||
("色尼区", "那曲县")
|
||||
)
|
||||
|
||||
def normalizeName(provinceIn: String = "", cityIn: String = "", districtIn: String = "", streetIn: String = "", isFullMatch: Boolean = false): List[Admin] = {
|
||||
val province = if (provinceIn == null || provinceIn.equals("未知")) "" else provinceIn
|
||||
val city = if (cityIn == null || cityIn.equals("未知")) "" else cityNameMap.getOrElse(cityIn, cityIn)
|
||||
val district = if (districtIn == null || districtIn.equals("未知")) "" else districtNameMap.getOrElse(districtIn, districtIn)
|
||||
val street = if (streetIn == null || streetIn.equals("未知")) "" else streetIn
|
||||
|
||||
val provinceAd = adminData.values.filter(s => s.level.equals(DistrictLevel.Province)).filter(s => StringUtils.isEmpty(province) || s.name.equals(province) || (!isFullMatch && s.shortName.equals(province)))
|
||||
val adminNodes = if (StringUtils.isEmpty(city) && StringUtils.isEmpty(district) && StringUtils.isEmpty(street)) provinceAd
|
||||
else {
|
||||
val cityAd = provinceAd.flatMap(s => s.children.map(adCode => adminData.getOrElse(adCode, streetData.get(adCode).orNull)))
|
||||
.filter(s => !s.level.equals(DistrictLevel.City) || s.level.equals(DistrictLevel.City) && (StringUtils.isEmpty(city) || s.name.equals(city) || (!isFullMatch && s.shortName.equals(city))))
|
||||
if (cityAd.isEmpty) provinceAd
|
||||
else {
|
||||
if (StringUtils.isEmpty(district) && StringUtils.isEmpty(street)) cityAd.filter(s => s.level.equals(DistrictLevel.Province) || s.level.equals(DistrictLevel.City))
|
||||
else {
|
||||
var districtAd = cityAd.flatMap(s => {
|
||||
if (s.level.equals(DistrictLevel.City) && StringUtils.isEmpty(street)) s.children.map(adCode => adminData.get(adCode).orNull)
|
||||
else if (s.level.equals(DistrictLevel.City)) s.children.map(adCode => adminData.getOrElse(adCode, streetData.get(adCode).orNull))
|
||||
else List(s)
|
||||
})
|
||||
.filter(s => s != null)
|
||||
.filter(s => !s.level.equals(DistrictLevel.District) ||
|
||||
s.level.equals(DistrictLevel.District) &&
|
||||
(StringUtils.isEmpty(district) || s.name.equals(district) || (!isFullMatch && s.shortName.equals(district))))
|
||||
|
||||
if (districtAd.isEmpty) districtAd = cityAd
|
||||
if (StringUtils.isEmpty(street)) districtAd.filter(s => s.level.equals(DistrictLevel.Province) || s.level.equals(DistrictLevel.City) || s.level.equals(DistrictLevel.District))
|
||||
else {
|
||||
var streetAd = districtAd.flatMap(s => if (s.level.equals(DistrictLevel.District)) s.children.map(adCode => streetData.get(adCode).orNull) else List(s))
|
||||
.filter(s => s.name.equals(street) || (!isFullMatch && s.shortName.equals(street)))
|
||||
if (streetAd.isEmpty) streetAd = districtAd
|
||||
streetAd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adminNodes.map(admin => {
|
||||
admin.level match {
|
||||
case DistrictLevel.Province => Admin.createProvince(admin.name, admin.id, admin.center)
|
||||
case DistrictLevel.City =>
|
||||
val province = adminData.get(admin.parentId).orNull
|
||||
Admin.createCity(province.name, admin.name, province.id, admin.id, admin.center)
|
||||
case DistrictLevel.District =>
|
||||
val city = adminData.get(admin.parentId).orNull
|
||||
val province = if (city.level == DistrictLevel.City) adminData.get(city.parentId).orNull else city
|
||||
Admin.createDistrict(province.name, city.name, admin.name, province.id, city.id, admin.id, admin.center)
|
||||
case DistrictLevel.Street =>
|
||||
val district = adminData.get(admin.parentId).orNull
|
||||
val city = if (district.level == DistrictLevel.District) adminData.get(district.parentId).orNull else district
|
||||
val province = if (city.level == DistrictLevel.City) adminData.get(city.parentId).orNull else city
|
||||
Admin.createStreet(province.name, city.name, district.name, admin.name, province.id, city.id, district.id, admin.id, admin.center)
|
||||
case _ => Admin.createOversea
|
||||
}
|
||||
}).toList
|
||||
}
|
||||
|
||||
private def determineAdminCode(lonIn: Double, latIn: Double, coordSys: CoordinateSystem = CoordinateSystem.GCJ02): Int = {
|
||||
val gcj02LonLat = GeoUtils.toGCJ02(lonIn, latIn, coordSys)
|
||||
val lon = gcj02LonLat._1
|
||||
val lat = gcj02LonLat._2
|
||||
|
||||
val s2LatLng = S2LatLng.fromDegrees(lat, lon)
|
||||
val id = S2CellId.fromLatLng(s2LatLng).parent(min_level).id()
|
||||
val id2 = S2CellId.fromLatLng(s2LatLng).parent(min_level - 2).id()
|
||||
if (GeoUtils.outOfChina(lon, lat)) {
|
||||
-1
|
||||
} else if (boundaryAdminCell.contains(id)) {
|
||||
boundaryAdminCell.getOrElse(id, -1)
|
||||
} else if (boundaryAdminCell.contains(id2)) {
|
||||
boundaryAdminCell.getOrElse(id2, -1)
|
||||
} else {
|
||||
var keys = List.empty[Long]
|
||||
var maxLevel = 2000 //必须大于2000m,否则会出现格子半径过小选择错误问题
|
||||
|
||||
// 最远距离 为新疆哈密市80公里
|
||||
while (keys.isEmpty && maxLevel < 200000) {
|
||||
keys = S2Utils.getCellId(s2LatLng, maxLevel, min_level)
|
||||
.flatMap(key => boundaryIndex.getOrElse(key, List.empty))
|
||||
maxLevel = maxLevel * 2
|
||||
}
|
||||
if (keys.nonEmpty) {
|
||||
val lines1 = keys
|
||||
.map(key => (key, new S2CellId(key).toLatLng.getEarthDistance(s2LatLng)))
|
||||
.sortBy(_._2)
|
||||
.take(5)
|
||||
.flatMap(startPoint => boundaryData.getOrElse(startPoint._1, List.empty).flatMap(value => List((startPoint._1, value._1, value._2, true), (startPoint._1, value._1, value._3, false))))
|
||||
.map(line => {
|
||||
val start = new S2CellId(line._1).toLatLng
|
||||
val end = new S2CellId(line._2).toLatLng
|
||||
val dis = LineUtils.pointToLineDis(start.lngDegrees(), start.latDegrees(), end.lngDegrees(), end.latDegrees(), lon, lat)
|
||||
(((start.lngDegrees(), start.latDegrees()), (end.lngDegrees(), end.latDegrees()), line._3, line._4), dis)
|
||||
})
|
||||
|
||||
// 取出所有距离最短的线段
|
||||
val minDis = lines1.map(_._2).min
|
||||
val lines = lines1.filter(s => s._2 == minDis).map(_._1)
|
||||
.groupBy(_._1)
|
||||
.maxBy(_._2.size)
|
||||
._2
|
||||
|
||||
if (lines.size == 1) { // 国内海外边界
|
||||
val line1 = lines.head
|
||||
val start = line1._1
|
||||
val end = line1._2
|
||||
// 三点用行列式判断旋转方向
|
||||
val angle = (start._1 - lon) * (end._2 - lat) - (end._1 - lon) * (start._2 - lat)
|
||||
if ((angle < 0) == line1._4) line1._3
|
||||
else -1
|
||||
} else if (lines.size == 2) { // 两条线段,如果终点不同,则一定是国内和海外,并且点到线段距离最短点为起点,终点相同,则为国内两个区域边界
|
||||
val line1 = lines.head
|
||||
val line2 = lines.last
|
||||
|
||||
// 终点相同,为国内两个相邻区域,终点不同,为国界线
|
||||
val start = if (line1._2.equals(line2._2)) line1._1 else line2._2
|
||||
val end = line1._2
|
||||
|
||||
// 三点用行列式判断旋转方向
|
||||
val angle = (start._1 - lon) * (end._2 - lat) - (end._1 - lon) * (start._2 - lat)
|
||||
if ((angle < 0) == line1._4) line1._3
|
||||
else if (line1._2 == line2._2 && line1._4 != line2._4) line2._3
|
||||
else -1
|
||||
} else { //多区域顶点 判断
|
||||
lines.groupBy(_._3).map(s => {
|
||||
val line1 = s._2.filter(_._4).head
|
||||
val line2 = s._2.filter(!_._4).head
|
||||
|
||||
var start = line2._2
|
||||
var end = line1._2
|
||||
val point = line1._1
|
||||
|
||||
val dis1 = LineUtils.lineDis(start._1, start._2, point._1, point._2)
|
||||
val dis2 = LineUtils.lineDis(end._1, end._2, point._1, point._2)
|
||||
if (dis1 > dis2)
|
||||
start = (point._1 + dis2 / dis1 * (start._1 - point._1), point._2 + dis2 / dis1 * (start._2 - point._2))
|
||||
else
|
||||
end = (point._1 + dis1 / dis2 * (end._1 - point._1), point._2 + dis1 / dis2 * (end._2 - point._2))
|
||||
val angle = (start._1 - lon) * (end._2 - lat) - (end._1 - lon) * (start._2 - lat)
|
||||
(s._1, angle)
|
||||
})
|
||||
.minBy(_._2)
|
||||
._1
|
||||
}
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def determineAreaByAdmin(lon: Double, lat: Double, admin: Admin, radius: Int): BusinessAreaInfo = {
|
||||
val businessAreas = determineAreaByCityId(lon, lat, admin.cityCode, radius, CoordinateSystem.GCJ02)
|
||||
BusinessAreaInfo(admin, businessAreas)
|
||||
}
|
||||
|
||||
def determineAreaByCityId(lon: Double, lat: Double, cityID: Int, radius: Int, coordSys: CoordinateSystem): Seq[BusinessArea] = {
|
||||
val gcj02LonLat = GeoUtils.toGCJ02(lon, lat, coordSys)
|
||||
if (cityID != -1) cityBusinessArea.getOrElse(cityID, Array.empty)
|
||||
.map(area => (GeoUtils.distance(area.center, Location(gcj02LonLat._1, gcj02LonLat._2)), area))
|
||||
.filter(_._1 <= radius)
|
||||
.sortBy(_._1)(Ordering.Double)
|
||||
.map(s => BusinessArea(s._2.name, s._2.areaCode, s._1.toInt))
|
||||
else Seq.empty
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.build
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.{AdminBoundary, AdminNode, CellAdmin}
|
||||
import com.dengxq.lnglat2Geo.utils.ObjectSerializer
|
||||
import com.google.common.geometry._
|
||||
import scala.collection.JavaConverters._
|
||||
import org.json4s.DefaultFormats
|
||||
import org.json4s.jackson.JsonMethods.parse
|
||||
|
||||
import scala.io.{Codec, Source}
|
||||
import scala.util.Try
|
||||
|
||||
object AdminDataProvider {
|
||||
|
||||
final val CHINA_DISTRICT_BOUNDARY = "src/main/resources/china/boundary.data"
|
||||
final val CHINA_DISTRICT_BOUNDARY2 = "data/china/boundary.data"
|
||||
final val CHINA_DISTRICT_BOUNDARY3 = "data/china/boundary3.data"
|
||||
final val RESOURCE_CHINA_DISTRICT_BOUNDARY = "/china/boundary.data"
|
||||
|
||||
final val CHINA_DISTRICT_BOUNDARY_CELL = "src/main/resources/china/boundaryCell.data"
|
||||
final val RESOURCE_CHINA_DISTRICT_BOUNDARY_CELL = "/china/boundaryCell.data"
|
||||
|
||||
final val CHINA_DISTRICT_ADMIN = "src/main/resources/china/admin.data"
|
||||
final val RESOURCE_CHINA_DISTRICT_ADMIN = "/china/admin.data"
|
||||
|
||||
final val CHINA_FILE_NAME_STREETS = "src/main/resources/china/street.data"
|
||||
final val RESOURCE_CHINA_FILE_NAME_STREETS = "/china/street.data"
|
||||
|
||||
final val RESOURCE_COUNTRY_CODE = "/china/countryCode"
|
||||
|
||||
private[lnglat2Geo] case class AMapData(
|
||||
citycode: Option[String],
|
||||
adcode: String,
|
||||
name: String,
|
||||
polyline: Option[String],
|
||||
center: String,
|
||||
level: String,
|
||||
var districts: Array[AMapData]
|
||||
)
|
||||
|
||||
object DistrictLoader {
|
||||
def loadAMapJson(fileName: String)(implicit mf: scala.reflect.Manifest[AMapData]): Option[AMapData] = {
|
||||
Try {
|
||||
implicit val formats = DefaultFormats
|
||||
val jsonStr = Source.fromFile("data/china/" + fileName)(Codec.UTF8).mkString
|
||||
val data = parse(jsonStr).extract[AMapData]
|
||||
Option(data)
|
||||
}.getOrElse(None)
|
||||
}
|
||||
|
||||
private def loadS2CellUnion(polylineString: String, minLevel: Int = -1, maxLevel: Int = -1, maxCells: Int = -1)
|
||||
: S2CellUnion = {
|
||||
val s2PolygonBuilder = new S2PolygonBuilder()
|
||||
|
||||
// polyline 中 | 分割多个polygon, 每个polygon的点用 ; 分割.
|
||||
// 每个 polygon 构建 一个 S2Loop, 合并到 S2PolygonBuilder中生成 multi polygon
|
||||
polylineString.split('|').foreach(loopStr => {
|
||||
val points: Array[S2Point] = loopStr.split(';').map(coordStr => {
|
||||
val parts = coordStr.split(',')
|
||||
// (parts(0).toDouble, parts(1).toDouble)
|
||||
val lng = parts(0).toDouble
|
||||
val lat = parts(1).toDouble
|
||||
S2LatLng.fromDegrees(lat, lng).toPoint
|
||||
})
|
||||
val jPoints = points.toList.asJava
|
||||
|
||||
s2PolygonBuilder.addLoop(new S2Loop(jPoints))
|
||||
})
|
||||
|
||||
val polygon = s2PolygonBuilder.assemblePolygon()
|
||||
|
||||
// 栅格化处理区域多边形
|
||||
val coverer = new S2RegionCoverer()
|
||||
if (minLevel > 0) {
|
||||
coverer.setMinLevel(minLevel)
|
||||
}
|
||||
if (maxLevel > 0) {
|
||||
coverer.setMaxLevel(maxLevel)
|
||||
}
|
||||
if (maxCells > 0) {
|
||||
coverer.setMaxCells(maxCells)
|
||||
}
|
||||
|
||||
//val s2CellUnion = coverer.getInteriorCovering(polygon)
|
||||
val s2CellUnion = coverer.getCovering(polygon)
|
||||
s2CellUnion.normalize()
|
||||
s2CellUnion
|
||||
}
|
||||
}
|
||||
|
||||
object AdminLoader {
|
||||
def loadBoundarySrc: Map[Int, Array[Array[Long]]] = {
|
||||
val stream = this.getClass.getResourceAsStream(RESOURCE_CHINA_DISTRICT_BOUNDARY)
|
||||
ObjectSerializer
|
||||
.deserialize[Array[AdminBoundary]](stream)
|
||||
.map(s => (s.code, s.boundary))
|
||||
.toMap
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载全国行政区划边界
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
def loadBoundaryData: Map[Long, List[(Long, Int, Int)]] = {
|
||||
val stream = this.getClass.getResourceAsStream(RESOURCE_CHINA_DISTRICT_BOUNDARY)
|
||||
ObjectSerializer
|
||||
.deserialize[Array[AdminBoundary]](stream)
|
||||
.flatMap(dis => dis.boundary.flatMap(line =>
|
||||
line.toList.sliding(2, 1).flatMap(s =>
|
||||
List((s.head, s.last, dis.code, true), (s.last, s.head, dis.code, false)))
|
||||
))
|
||||
.toList
|
||||
.groupBy(_._1)
|
||||
.map(s => (s._1, s._2.map(ss => (ss._2, ss._3, ss._4)).groupBy(_._1)
|
||||
.map(sss => {
|
||||
val list = sss._2.map(ssss => (ssss._2, ssss._3)).sortBy(_._2)(Ordering.Boolean.reverse)
|
||||
if (list.size > 2) throw new Exception
|
||||
else if (list.size == 2) {
|
||||
if (!list.head._2 || list.last._2) throw new Exception
|
||||
val first = list.head._1
|
||||
val second = list.last._1
|
||||
(sss._1, first, second)
|
||||
} else {
|
||||
if (list.head._2) (sss._1, list.head._1, -1)
|
||||
else (sss._1, -1, list.head._1)
|
||||
}
|
||||
}).toList
|
||||
))
|
||||
}
|
||||
|
||||
def loadBoundaryCellData: Map[Long, Int] = {
|
||||
val stream = this.getClass.getResourceAsStream(RESOURCE_CHINA_DISTRICT_BOUNDARY_CELL)
|
||||
ObjectSerializer
|
||||
.deserialize[Array[CellAdmin]](stream)
|
||||
.map(cellAdmin => {
|
||||
(cellAdmin.cellId, cellAdmin.adCode)
|
||||
})
|
||||
.toMap
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载全国行政区划数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
def loadAdminData: Map[Int, AdminNode] = {
|
||||
val stream = this.getClass.getResourceAsStream(RESOURCE_CHINA_DISTRICT_ADMIN)
|
||||
ObjectSerializer.deserialize[Array[AdminNode]](stream)
|
||||
.map(s => (s.id, s)).toMap
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载全国街道数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
def loadStreetData: Array[AdminNode] = {
|
||||
val stream = this.getClass.getResourceAsStream(RESOURCE_CHINA_FILE_NAME_STREETS)
|
||||
ObjectSerializer.deserialize[Array[AdminNode]](stream)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载世界国家code
|
||||
* @return
|
||||
*/
|
||||
def loadCountryCode : Map[String, String] = {
|
||||
val url = getClass.getResource(RESOURCE_COUNTRY_CODE)
|
||||
Source.fromURL(url)(Codec.UTF8)
|
||||
.getLines()
|
||||
.map(s => {
|
||||
val Array(code:String, name:String) = s.split(",")
|
||||
(code, name)
|
||||
})
|
||||
.toMap
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.build
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity._
|
||||
import com.dengxq.lnglat2Geo.utils.ObjectSerializer
|
||||
|
||||
import scala.io.{Codec, Source}
|
||||
|
||||
object CityAreaDataProvider {
|
||||
|
||||
final val BUSINESS_AREA_DATA = "src/main/resources/china/area.data"
|
||||
final val RESOURCE_BUSINESS_AREA_DATA = "/china/area.data"
|
||||
|
||||
/**
|
||||
* 加载全国商圈数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
def loadBusinessAreaData: Map[Int, Array[BusinessAreaData]] = {
|
||||
val stream = this.getClass.getResourceAsStream(RESOURCE_BUSINESS_AREA_DATA)
|
||||
ObjectSerializer.deserialize[Array[BusinessAreaGroup]](stream)
|
||||
.map(s => (s.cityAdCode, s.areas))
|
||||
.toMap
|
||||
}
|
||||
|
||||
def loadBusinessArea: Array[BusinessAreaGroup] = {
|
||||
val areaCodeMap: Map[String, Int] = loadBusinessAreaCode
|
||||
Source.fromFile("data/china/cityareas")(Codec.UTF8)
|
||||
.getLines()
|
||||
.map(line => {
|
||||
val Array(cityAdcode, province, city, district, area_name, strLng, strLat) = line.split(",")
|
||||
val areaCode = areaCodeMap.getOrElse(List(province, city, area_name).mkString(","), -1)
|
||||
(cityAdcode.toInt, BusinessAreaData(area_name, Location(strLng.toDouble, strLat.toDouble), areaCode))
|
||||
})
|
||||
.toArray
|
||||
.groupBy(_._1)
|
||||
.map(s => BusinessAreaGroup(s._1, s._2.map(_._2)))
|
||||
.toArray
|
||||
}
|
||||
|
||||
def loadBusinessAreaCode: Map[String, Int] = {
|
||||
Source.fromFile("data/china/busineesAreaCode")(Codec.UTF8)
|
||||
.getLines()
|
||||
.map(line => {
|
||||
val Array(adCode, area) = line.split(",")
|
||||
val Array(country, province, city, areaName) = area.split("/")
|
||||
(List(province, city, areaName).mkString(","), adCode.substring(2).toInt)
|
||||
}).toMap
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.build
|
||||
|
||||
import scala.io.{Codec, Source}
|
||||
|
||||
object CityLevelDataProvider {
|
||||
|
||||
def csv2Map: Map[String, String] = {
|
||||
val fname = "/china/citylevels"
|
||||
val url = getClass.getResource(fname)
|
||||
Source.fromURL(url)(Codec.UTF8)
|
||||
.getLines()
|
||||
.flatMap(line => {
|
||||
val Array(name, adCode, cityLevel) = line.split(",")
|
||||
List((name, cityLevel), (adCode, cityLevel))
|
||||
})
|
||||
.toMap
|
||||
}
|
||||
}
|
@ -1,396 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.build
|
||||
|
||||
import java.nio.file.Paths
|
||||
import java.text.Collator
|
||||
|
||||
import com.dengxq.lnglat2Geo.build.AdminDataProvider.{AMapData, DistrictLoader}
|
||||
import com.google.common.geometry._
|
||||
import com.dengxq.lnglat2Geo.entity._
|
||||
import com.dengxq.lnglat2Geo.utils.{AdminUtils, GeoUtils, ObjectSerializer, S2Utils}
|
||||
import com.dengxq.lnglat2Geo.utils.S2Utils.{childrenCellId, getLevel}
|
||||
import com.dengxq.lnglat2Geo.GeoTransImpl.min_level
|
||||
import AdminDataProvider._
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import collection.JavaConversions._
|
||||
|
||||
object Upgrade {
|
||||
|
||||
private val MAX_INTERVER = 500
|
||||
|
||||
/**
|
||||
* 加载全部区县的边界数据,拆分过长线段,每段不超过500m
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private def loadBoundary(): Array[AdminBoundary] = {
|
||||
DistrictLoader.loadAMapJson("country/" + 100000 + ".json")
|
||||
.get
|
||||
.districts
|
||||
.flatMap(province => {
|
||||
val provinceData = DistrictLoader.loadAMapJson("province/" + province.adcode + ".json")
|
||||
if (provinceData.nonEmpty) {
|
||||
val cityDatas = provinceData.get
|
||||
.districts
|
||||
.flatMap(city => {
|
||||
val cityData = DistrictLoader.loadAMapJson("city/" + city.adcode + ".json")
|
||||
if (cityData.nonEmpty) {
|
||||
val districtDatas = cityData.get.districts.map(dis => DistrictLoader.loadAMapJson("district/" + dis.adcode + ".json"))
|
||||
if (districtDatas.exists(s => s.nonEmpty && s.get.polyline.nonEmpty)) districtDatas
|
||||
else List(cityData)
|
||||
} else {
|
||||
val districtData = DistrictLoader.loadAMapJson("district/" + city.adcode + ".json")
|
||||
if (districtData.nonEmpty && districtData.get.polyline.nonEmpty) List(districtData)
|
||||
else List.empty
|
||||
}
|
||||
})
|
||||
if (cityDatas.exists(s => s.nonEmpty && s.get.polyline.nonEmpty)) cityDatas
|
||||
else List(provinceData)
|
||||
} else List.empty
|
||||
})
|
||||
.filter(_.nonEmpty)
|
||||
.flatMap(district => {
|
||||
if (district.get.polyline.nonEmpty) {
|
||||
val data = district.get.polyline.get.split('|').map(loopStr => {
|
||||
val ss = loopStr.split(';')
|
||||
.map(coordStr => {
|
||||
val parts = coordStr.split(',')
|
||||
val wgs84 = GeoUtils.gcj02ToWgs84(parts(0).toDouble, parts(1).toDouble)
|
||||
(wgs84._1, wgs84._2, S2LatLng.fromDegrees(wgs84._2, wgs84._1))
|
||||
})
|
||||
.sliding(2, 1)
|
||||
.flatMap(slice => {
|
||||
val dis = slice.head._3.getEarthDistance(slice.last._3).toInt
|
||||
val n = dis / MAX_INTERVER + 1
|
||||
(0 to n)
|
||||
.map(i => (slice.head._1 + (slice.last._1 - slice.head._1) * i / n, slice.head._2 + (slice.last._2 - slice.head._2) * i / n))
|
||||
.sliding(2, 1)
|
||||
})
|
||||
.toArray
|
||||
(ss.map(_.head) ++ List(ss.last.last))
|
||||
.map(s => S2CellId.fromLatLng(S2LatLng.fromDegrees(s._2, s._1)).id())
|
||||
})
|
||||
List(AdminBoundary(district.get.adcode.toInt, data))
|
||||
}
|
||||
else List.empty
|
||||
})
|
||||
}
|
||||
|
||||
def loadBoundary2(): Array[AdminBoundary2] = {
|
||||
DistrictLoader.loadAMapJson("country/" + 100000 + ".json")
|
||||
.get
|
||||
.districts
|
||||
.flatMap(province => {
|
||||
val provinceData = DistrictLoader.loadAMapJson("province/" + province.adcode + ".json")
|
||||
if (provinceData.nonEmpty) {
|
||||
val cityDatas = provinceData.get
|
||||
.districts
|
||||
.flatMap(city => {
|
||||
val cityData = DistrictLoader.loadAMapJson("city/" + city.adcode + ".json")
|
||||
if (cityData.nonEmpty) {
|
||||
val districtDatas = cityData.get.districts.map(dis => DistrictLoader.loadAMapJson("district/" + dis.adcode + ".json"))
|
||||
if (districtDatas.exists(s => s.nonEmpty && s.get.polyline.nonEmpty)) districtDatas
|
||||
else List(cityData)
|
||||
} else {
|
||||
val districtData = DistrictLoader.loadAMapJson("district/" + city.adcode + ".json")
|
||||
if (districtData.nonEmpty && districtData.get.polyline.nonEmpty) List(districtData)
|
||||
else List.empty
|
||||
}
|
||||
})
|
||||
if (cityDatas.exists(s => s.nonEmpty && s.get.polyline.nonEmpty)) cityDatas
|
||||
else List(provinceData)
|
||||
} else List.empty
|
||||
})
|
||||
.filter(_.nonEmpty)
|
||||
.flatMap(district => {
|
||||
if (district.get.polyline.nonEmpty) {
|
||||
val data = district.get.polyline.get.split('|').map(loopStr => {
|
||||
loopStr.split(';')
|
||||
.map(coordStr => {
|
||||
val parts = coordStr.split(',')
|
||||
(parts(0).toDouble, parts(1).toDouble)
|
||||
})
|
||||
})
|
||||
|
||||
val points = data.flatMap(_.toList)
|
||||
val mbr = (points.map(_._1).min, points.map(_._1).max, points.map(_._2).min, points.map(_._2).max)
|
||||
|
||||
val boundary = data.map(s => s.map(ss => S2CellId.fromLatLng(S2LatLng.fromDegrees(ss._2, ss._1)).id()))
|
||||
|
||||
List(AdminBoundary2(district.get.adcode.toInt, mbr, boundary))
|
||||
}
|
||||
else List.empty
|
||||
})
|
||||
}
|
||||
|
||||
def genCellAdmin(): Array[CellAdmin] = {
|
||||
|
||||
// 国内区县边界S2块
|
||||
val boundaryIndex: Map[Long, List[Long]] =
|
||||
AdminDataProvider.AdminLoader.loadBoundaryData
|
||||
.keySet
|
||||
.map(s => (new S2CellId(s).parent(min_level).id(), s))
|
||||
.groupBy(_._1)
|
||||
.map(s => (s._1, s._2.map(_._2).toList))
|
||||
|
||||
// 计算国内区县非边界线区域的S2块
|
||||
val chinaCellAdmin = DistrictLoader.loadAMapJson("country/" + 100000 + ".json")
|
||||
.get
|
||||
.districts
|
||||
.flatMap(province => {
|
||||
val provinceData = DistrictLoader.loadAMapJson("province/" + province.adcode + ".json")
|
||||
if (provinceData.nonEmpty) {
|
||||
val cityDatas = provinceData.get
|
||||
.districts
|
||||
.flatMap(city => {
|
||||
val cityData = DistrictLoader.loadAMapJson("city/" + city.adcode + ".json")
|
||||
if (cityData.nonEmpty) {
|
||||
val districtDatas = cityData.get.districts.map(dis => DistrictLoader.loadAMapJson("district/" + dis.adcode + ".json"))
|
||||
if (districtDatas.exists(s => s.nonEmpty && s.get.polyline.nonEmpty)) districtDatas
|
||||
else List(cityData)
|
||||
} else {
|
||||
val districtData = DistrictLoader.loadAMapJson("district/" + city.adcode + ".json")
|
||||
if (districtData.nonEmpty && districtData.get.polyline.nonEmpty) List(districtData)
|
||||
else List.empty
|
||||
}
|
||||
})
|
||||
if (cityDatas.exists(s => s.nonEmpty && s.get.polyline.nonEmpty)) cityDatas
|
||||
else List(provinceData)
|
||||
} else List.empty
|
||||
})
|
||||
.flatMap(admin => {
|
||||
if (admin.nonEmpty && admin.get.polyline.nonEmpty)
|
||||
S2Utils.loadS2CellUnion(admin.get.polyline.get, min_level, min_level, 1000, true)
|
||||
.cellIds()
|
||||
.flatMap(s2CellId => {
|
||||
val cellLevel = getLevel(s2CellId.id)
|
||||
if (cellLevel == min_level) List(s2CellId.id())
|
||||
else childrenCellId(s2CellId, cellLevel, min_level).map(_.id()).toList
|
||||
})
|
||||
.distinct
|
||||
.map(s => (s, boundaryIndex.getOrElse(s, List.empty)))
|
||||
.filter(_._2.isEmpty)
|
||||
.map(s => (s._1, admin.get.adcode.toInt))
|
||||
.toList
|
||||
else List.empty
|
||||
})
|
||||
.groupBy(_._1)
|
||||
.filter(_._2.length == 1)
|
||||
.flatMap(s => s._2)
|
||||
.toArray
|
||||
|
||||
// 计算中国国界线包含的所有区域
|
||||
val chinaPoly = DistrictLoader.loadAMapJson("country/" + 100000 + ".json")
|
||||
.get.polyline.get
|
||||
val chinaCell = S2Utils.loadS2CellUnion(chinaPoly, min_level, min_level, 10000, false)
|
||||
.cellIds()
|
||||
.flatMap(s2CellId => {
|
||||
val cellLevel = getLevel(s2CellId.id)
|
||||
if (cellLevel == min_level) List(s2CellId.id())
|
||||
else if (cellLevel < min_level) childrenCellId(s2CellId, cellLevel, min_level).map(_.id()).toList
|
||||
else List.empty
|
||||
})
|
||||
.distinct
|
||||
.map(s => (s, 1))
|
||||
.toMap
|
||||
|
||||
// 计算中国最小外接矩形内的非中国区域
|
||||
val startS2: S2LatLng = S2LatLng.fromDegrees(0.8293, 72.004) //左下角
|
||||
val endS2: S2LatLng = S2LatLng.fromDegrees(55.8271, 137.8347) //右上角
|
||||
val rect: S2LatLngRect = new S2LatLngRect(startS2, endS2)
|
||||
val coverer: S2RegionCoverer = new S2RegionCoverer
|
||||
coverer.setMaxLevel(7)
|
||||
coverer.setMinLevel(7)
|
||||
coverer.getCovering(rect).cellIds()
|
||||
.flatMap(s2CellId => {
|
||||
val cellLevel = getLevel(s2CellId.id)
|
||||
if (cellLevel == min_level) List(s2CellId.id())
|
||||
else if (cellLevel < min_level) childrenCellId(s2CellId, cellLevel, min_level).map(_.id()).toList
|
||||
else List.empty
|
||||
})
|
||||
.distinct
|
||||
.map(s => (s, chinaCell.getOrElse(s, -1)))
|
||||
.filter(_._2 == -1)
|
||||
.union(chinaCellAdmin)
|
||||
.map(s => {
|
||||
val parent = new S2CellId(s._1).parent(10).id()
|
||||
(parent, s._2, s._1)
|
||||
})
|
||||
.groupBy(s => (s._1, s._2))
|
||||
.flatMap(s => {
|
||||
if (s._2.size == 16) List(CellAdmin(s._1._2, s._1._1))
|
||||
else s._2.map(ss => CellAdmin(ss._2, ss._3))
|
||||
})
|
||||
.toArray
|
||||
}
|
||||
|
||||
private def encodeTownship(): Array[AdminNode] = {
|
||||
loadChinaAdmin(AMapData(None, "100000", "", None, "", "country", Array.empty), 0)
|
||||
.filter(s => s.level.equals(DistrictLevel.Street))
|
||||
.groupBy(_.id)
|
||||
.flatMap(admin =>
|
||||
admin._2
|
||||
.sortWith((a, b) => Collator.getInstance(java.util.Locale.SIMPLIFIED_CHINESE).compare(a.name, b.name) < 0)
|
||||
.zipWithIndex
|
||||
.map(s => {
|
||||
s._1.id = s._1.id * 100 + s._2 + 1
|
||||
s._1
|
||||
})
|
||||
).toArray
|
||||
}
|
||||
|
||||
private def loadChinaDistrict(): Array[AdminNode] = {
|
||||
val streetMap = AdminDataProvider.AdminLoader.loadStreetData
|
||||
.map(s => (s.parentId, s.id))
|
||||
.groupBy(_._1)
|
||||
.map(s => (s._1, s._2.map(_._2).toList))
|
||||
|
||||
Upgrade.loadChinaAdmin(AMapData(None, "100000", "", None, "", "country", Array.empty), 0)
|
||||
.filter(s => !s.level.equals(DistrictLevel.Street))
|
||||
.map(s => {
|
||||
if (s.children.isEmpty) s.children = streetMap.getOrElse(s.id, List.empty)
|
||||
s
|
||||
}).toArray
|
||||
}
|
||||
|
||||
def loadChinaAdmin(aMapData: AMapData, parentId: Int): List[AdminNode] = {
|
||||
if (aMapData.level != "street") {
|
||||
val nodeData = DistrictLoader.loadAMapJson(aMapData.level + "/" + aMapData.adcode + ".json").orNull
|
||||
if (nodeData != null) {
|
||||
val name = if (nodeData.level == "country") "中国" else nodeData.name // 硬编码 "中国", 踢掉json中"中华人民共和国"
|
||||
val level = DistrictLevel.withName(nodeData.level)
|
||||
List(AdminNode(nodeData.adcode.toInt, name, AdminUtils.shortAdmin(name, level), Location.fromLngLatString(nodeData.center), level,
|
||||
parentId, if (nodeData.districts.nonEmpty && nodeData.districts.head.level != "street") nodeData.districts.map(_.adcode.toInt).toList else List.empty)) ++
|
||||
nodeData.districts.flatMap(admin => loadChinaAdmin(admin, nodeData.adcode.toInt)).toList
|
||||
} else List.empty
|
||||
} else {
|
||||
val level = DistrictLevel.withName(aMapData.level)
|
||||
List(AdminNode(aMapData.adcode.toInt, aMapData.name, AdminUtils.shortAdmin(aMapData.name, level), Location.fromLngLatString(aMapData.center), level, parentId, List.empty))
|
||||
}
|
||||
}
|
||||
|
||||
def upgradeAdminBoundary2(): Unit = {
|
||||
val data = loadBoundary2()
|
||||
ObjectSerializer.serialize(data, Paths.get(CHINA_DISTRICT_BOUNDARY2).toAbsolutePath.toString)
|
||||
}
|
||||
|
||||
|
||||
def upgradeAdminBoundary(): Unit = {
|
||||
val data = loadBoundary()
|
||||
ObjectSerializer.serialize(data, Paths.get(CHINA_DISTRICT_BOUNDARY).toAbsolutePath.toString)
|
||||
}
|
||||
|
||||
def upgradeAdminBoundaryCell(): Unit = {
|
||||
val data = genCellAdmin()
|
||||
ObjectSerializer.serialize(data, Paths.get(CHINA_DISTRICT_BOUNDARY_CELL).toAbsolutePath.toString)
|
||||
}
|
||||
|
||||
def upgradeChinaAdmin(): Unit = {
|
||||
val towns: Array[AdminNode] = loadChinaDistrict()
|
||||
ObjectSerializer.serialize(towns, Paths.get(CHINA_DISTRICT_ADMIN).toAbsolutePath.toString)
|
||||
}
|
||||
|
||||
def upgradeTownship(): Unit = {
|
||||
val towns: Array[AdminNode] = encodeTownship()
|
||||
ObjectSerializer.serialize(towns, Paths.get(CHINA_FILE_NAME_STREETS).toAbsolutePath.toString)
|
||||
}
|
||||
|
||||
def upgradeBusinessAreaData(): Unit = {
|
||||
val obj = CityAreaDataProvider.loadBusinessArea
|
||||
ObjectSerializer.serialize(obj, Paths.get(CityAreaDataProvider.BUSINESS_AREA_DATA).toAbsolutePath.toString)
|
||||
}
|
||||
|
||||
def loadSharedLines(boundary: Array[AdminBoundary2]): Array[AdminCommonBoundary] = {
|
||||
var result = ArrayBuffer.empty[AdminCommonBoundary]
|
||||
for (i <- 0 until boundary.length - 1) {
|
||||
val admin1 = boundary(i)
|
||||
val bound1 = admin1.mbr
|
||||
println(i, admin1.code)
|
||||
for (j <- i + 1 until boundary.length) {
|
||||
val admin2 = boundary(j)
|
||||
val bound2 = admin2.mbr
|
||||
|
||||
val pair = if (bound1._1 > bound2._2 || bound2._1 > bound1._2 || bound1._3 > bound2._4 || bound2._3 > bound1._4) Array.empty[AdminCommonBoundary]
|
||||
else admin1.boundary.flatMap(line1 => {
|
||||
val line1Index = line1.zipWithIndex.toMap
|
||||
|
||||
val commons = admin2.boundary.flatMap(line2 =>
|
||||
line2.zipWithIndex
|
||||
.filter(s => line1.contains(s._1))
|
||||
.map(s => {
|
||||
val index1 = line1Index.getOrElse(s._1, 0)
|
||||
(s._1, s._2, index1, s._2 + index1)
|
||||
})
|
||||
.groupBy(_._4)
|
||||
.map(s => (admin2.code, admin1.code, s._2.sortBy(_._2))) // 线段方向是admin2的
|
||||
)
|
||||
|
||||
if (commons.nonEmpty) commons.map(s => AdminCommonBoundary(s._1, s._2, s._3.map(_._1)))
|
||||
else List.empty
|
||||
})
|
||||
result = result ++ pair
|
||||
}
|
||||
}
|
||||
result.toArray
|
||||
}
|
||||
|
||||
def loadOnesideLine(sharedLines: Array[AdminCommonBoundary], boundary: Array[AdminBoundary2]): Array[AdminCommonBoundary] = {
|
||||
val commons = sharedLines
|
||||
.flatMap(s => List((s.code, s.boundary), (s.code2, s.boundary)))
|
||||
.groupBy(_._1)
|
||||
.map(s => (s._1, s._2.flatMap(_._2.toList)))
|
||||
|
||||
boundary.flatMap(s => {
|
||||
val common: Array[Long] = commons.getOrElse(s.code, Array.empty)
|
||||
val result = ArrayBuffer.empty[ArrayBuffer[Long]]
|
||||
|
||||
s.boundary.foreach(ss => {
|
||||
var temp = ArrayBuffer.empty[Long]
|
||||
|
||||
ss.foreach(sss => {
|
||||
if (common.isEmpty || !common.contains(sss)) temp.append(sss)
|
||||
else if (temp.nonEmpty) {
|
||||
result.append(temp)
|
||||
temp = ArrayBuffer.empty[Long]
|
||||
}
|
||||
})
|
||||
if (temp.nonEmpty) result.append(temp)
|
||||
})
|
||||
|
||||
if (result.nonEmpty) {
|
||||
println("code = " + s.code)
|
||||
val sssss = result.map(s => s.map(s => {
|
||||
val latlng = new S2CellId(s).toLatLng
|
||||
List(latlng.lngDegrees(), latlng.latDegrees()).mkString(",")
|
||||
})).mkString(";")
|
||||
println(sssss)
|
||||
result.map(ss => AdminCommonBoundary(s.code, -1, ss.toArray))
|
||||
}
|
||||
else List.empty
|
||||
})
|
||||
}
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
// upgradeAdminBoundary2()
|
||||
|
||||
Upgrade.upgradeAdminBoundary() // 生成行政区划边界数据
|
||||
Upgrade.upgradeAdminBoundaryCell() // 生成行政区划边界数据2
|
||||
Upgrade.upgradeChinaAdmin() // 生成行政区划关系数据(不含街道)
|
||||
Upgrade.upgradeTownship() // 生成行政区划街道数据
|
||||
Upgrade.upgradeBusinessAreaData() // 生成商圈数据
|
||||
|
||||
// val data = loadBoundary2()
|
||||
//
|
||||
// val sharedLines = loadSharedLines(data)
|
||||
// val oneSide: Array[AdminCommonBoundary] = loadOnesideLine(sharedLines, data)
|
||||
|
||||
// ObjectSerializer.serialize(result.toArray, Paths.get(CHINA_DISTRICT_BOUNDARY2).toAbsolutePath.toString)
|
||||
// ObjectSerializer.serialize(oneSide, Paths.get(CHINA_DISTRICT_BOUNDARY3).toAbsolutePath.toString)
|
||||
|
||||
// val stream = this.getClass.getResourceAsStream(RESOURCE_CHINA_DISTRICT_BOUNDARY)
|
||||
// ObjectSerializer
|
||||
// .deserialize[Array[AdminCommonBoundary]](stream)
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
import com.dengxq.lnglat2Geo.utils.AdminUtils
|
||||
|
||||
case class Admin(country: String,
|
||||
province: String,
|
||||
city: String,
|
||||
district: String,
|
||||
town: String,
|
||||
level: String,
|
||||
countryCode: String,
|
||||
provinceCode: Int,
|
||||
cityCode: Int,
|
||||
districtCode: Int,
|
||||
townCode: Int,
|
||||
center: Location = null
|
||||
) {
|
||||
|
||||
def hasCenter: Boolean = center != Admin.UNKNOWN_LOCATION_VAL
|
||||
def hasProvince: Boolean = province != Admin.UNKNOWN_NAME_VAL
|
||||
def hasCity: Boolean = city != Admin.UNKNOWN_NAME_VAL
|
||||
def hasDistrict: Boolean = district != Admin.UNKNOWN_NAME_VAL
|
||||
def hasCityId: Boolean = cityCode != Admin.UNKNOWN_ID_VAL
|
||||
def hasDistrictId: Boolean = districtCode != Admin.UNKNOWN_ID_VAL
|
||||
def hasTown: Boolean = town != Admin.UNKNOWN_NAME_VAL
|
||||
def shortProvince :String = AdminUtils.shortProvince(province)
|
||||
def shortCity :String = AdminUtils.shortCity(city)
|
||||
def toShort: Admin = Admin(country,
|
||||
AdminUtils.shortProvince(province),
|
||||
AdminUtils.shortCity(city),
|
||||
AdminUtils.shortDistrict(district),
|
||||
AdminUtils.shortStreet(town),
|
||||
level, countryCode, provinceCode, cityCode, districtCode, townCode, center)
|
||||
def toNameString: String = s"$country${if(hasProvince) province else ""}${if(hasCity) city else ""}${if(hasDistrict) district else ""}${if(hasTown) town else ""}"
|
||||
}
|
||||
|
||||
private[lnglat2Geo] object Admin {
|
||||
private final val CHINA_NAME = "中国"
|
||||
private final val CHINA_ID = "CN"
|
||||
private final val OVERSEA_NAME_VAL = "海外"
|
||||
private final val UNKNOWN_NAME_VAL = "未知"
|
||||
private final val UNKNOWN_ID_VAL = -1
|
||||
private final val UNKNOWN_LOCATION_VAL = null
|
||||
|
||||
def createOversea = Admin(OVERSEA_NAME_VAL,
|
||||
province = OVERSEA_NAME_VAL,
|
||||
city = OVERSEA_NAME_VAL,
|
||||
district = OVERSEA_NAME_VAL,
|
||||
town = OVERSEA_NAME_VAL,
|
||||
level = AdminLevel.Oversea,
|
||||
countryCode = "",
|
||||
provinceCode = UNKNOWN_ID_VAL,
|
||||
cityCode = UNKNOWN_ID_VAL,
|
||||
districtCode = UNKNOWN_ID_VAL,
|
||||
townCode = UNKNOWN_ID_VAL,
|
||||
center = UNKNOWN_LOCATION_VAL
|
||||
)
|
||||
|
||||
def createCountry(country:String, countryID:String, center: Location) = Admin(
|
||||
country,
|
||||
province = UNKNOWN_NAME_VAL,
|
||||
city = UNKNOWN_NAME_VAL,
|
||||
district = UNKNOWN_NAME_VAL,
|
||||
town = UNKNOWN_NAME_VAL,
|
||||
level = AdminLevel.Country,
|
||||
countryCode = countryID,
|
||||
provinceCode = UNKNOWN_ID_VAL,
|
||||
cityCode = UNKNOWN_ID_VAL,
|
||||
districtCode = UNKNOWN_ID_VAL,
|
||||
townCode = UNKNOWN_ID_VAL,
|
||||
center = center
|
||||
)
|
||||
|
||||
def createProvince(province: String, provinceId: Int, center: Location) = Admin(
|
||||
country = CHINA_NAME,
|
||||
province = province,
|
||||
city = UNKNOWN_NAME_VAL,
|
||||
district = UNKNOWN_NAME_VAL,
|
||||
town = UNKNOWN_NAME_VAL,
|
||||
level = AdminLevel.Province,
|
||||
countryCode = CHINA_ID,
|
||||
provinceCode = provinceId,
|
||||
cityCode = UNKNOWN_ID_VAL,
|
||||
districtCode = UNKNOWN_ID_VAL,
|
||||
townCode = UNKNOWN_ID_VAL,
|
||||
center = center
|
||||
)
|
||||
|
||||
def createCity(province: String, city: String, provinceId: Int, cityId: Int, center: Location) = Admin(
|
||||
country = CHINA_NAME,
|
||||
province = province,
|
||||
city = city,
|
||||
district = UNKNOWN_NAME_VAL,
|
||||
town = UNKNOWN_NAME_VAL,
|
||||
level = AdminLevel.City,
|
||||
countryCode = CHINA_ID,
|
||||
provinceCode = provinceId,
|
||||
cityCode = cityId,
|
||||
districtCode = UNKNOWN_ID_VAL,
|
||||
townCode = UNKNOWN_ID_VAL,
|
||||
center = center
|
||||
)
|
||||
|
||||
def createProvincialCity(province: String, city: String, provinceId: Int, cityId: Int, center: Location) = Admin(
|
||||
country = CHINA_NAME,
|
||||
province = province,
|
||||
city = city,
|
||||
district = city,
|
||||
town = UNKNOWN_NAME_VAL,
|
||||
level = AdminLevel.ProvincialCity,
|
||||
countryCode = CHINA_ID,
|
||||
provinceCode = provinceId,
|
||||
cityCode = cityId,
|
||||
districtCode = cityId,
|
||||
townCode = UNKNOWN_ID_VAL,
|
||||
center = center
|
||||
)
|
||||
|
||||
def createDistrict(province: String, city: String, district: String,
|
||||
provinceId:Int, cityId: Int, districtId: Int, center: Location) = Admin(
|
||||
country = CHINA_NAME,
|
||||
province = province,
|
||||
city = city,
|
||||
district = district,
|
||||
town = UNKNOWN_NAME_VAL,
|
||||
level = AdminLevel.District,
|
||||
countryCode = CHINA_ID,
|
||||
provinceCode = provinceId,
|
||||
cityCode = cityId,
|
||||
districtCode = districtId,
|
||||
townCode = UNKNOWN_ID_VAL,
|
||||
center = center
|
||||
)
|
||||
|
||||
def createStreet(province: String, city: String, district: String, town: String,
|
||||
provinceId:Int, cityId: Int, districtId: Int, streetId: Int, center: Location) = Admin(
|
||||
country = CHINA_NAME,
|
||||
province = province,
|
||||
city = city,
|
||||
district = district,
|
||||
town = town,
|
||||
level = AdminLevel.Street,
|
||||
countryCode = CHINA_ID,
|
||||
provinceCode = provinceId,
|
||||
cityCode = cityId,
|
||||
districtCode = districtId,
|
||||
townCode = streetId,
|
||||
center = center
|
||||
)
|
||||
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
@SerialVersionUID(-4986836039872467881L)
|
||||
case class AdminBoundary(var code:Int, var boundary : Array[Array[Long]]) extends Serializable
|
@ -1,4 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
@SerialVersionUID(-1l)
|
||||
case class AdminBoundary2(var code: Int, var mbr: (Double, Double, Double, Double), var boundary: Array[Array[Long]]) extends Serializable
|
@ -1,4 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
@SerialVersionUID(-1)
|
||||
case class AdminCommonBoundary(var code:Int, var code2:Int, var boundary : Array[Long]) extends Serializable
|
@ -1,14 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.DistrictLevel.DistrictLevel
|
||||
|
||||
@SerialVersionUID(5953969594271885955L)
|
||||
case class AdminNode(
|
||||
var id: Int,
|
||||
name: String,
|
||||
shortName:String,
|
||||
center: Location,
|
||||
level: DistrictLevel,
|
||||
parentId: Int,
|
||||
var children: List[Int]
|
||||
) extends Serializable
|
@ -1,18 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
/**
|
||||
* 商圈信息
|
||||
* @param name 商圈名称
|
||||
* @param center 商圈中心点
|
||||
* @param areaCode 商圈ID
|
||||
* @param parentId 所在行政区划id
|
||||
*/
|
||||
@SerialVersionUID(-6474937368799868168L)
|
||||
case class BusinessAreaData(name: String, center: Location, areaCode: Int) extends Serializable
|
||||
|
||||
@SerialVersionUID(-5899680396800964972L)
|
||||
case class BusinessAreaGroup(cityAdCode: Int, areas: Array[BusinessAreaData]) extends Serializable
|
||||
|
||||
case class BusinessArea(name: String, areaCode: Int, distance:Int)
|
||||
|
||||
case class BusinessAreaInfo(admin: Admin, areas: Seq[BusinessArea])
|
@ -1,4 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
@SerialVersionUID(6694671327358697642L)
|
||||
case class CellAdmin(var adCode : Int, var cellId:Long) extends Serializable
|
@ -1,72 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
trait EnumFunc extends Enumeration {
|
||||
def exists(code: Int): Boolean = this.values.exists(_.id == code) //检测是否存在此枚举值
|
||||
def exists(code: Value): Boolean = this.values.contains(code) //检测是否存在此枚举值
|
||||
def exists(name: String): Boolean = Try {this.withName(name); true}.getOrElse(false)
|
||||
def getName(code: Int): String = this.values.find(_.id == code).getOrElse("").toString
|
||||
def getName(code: Value): String = this.values.find(_.id == code.id).getOrElse("").toString
|
||||
}
|
||||
|
||||
object EnumImplicit {
|
||||
implicit def enum2int[E<:Enumeration#Value](enum: E): Int = enum.id
|
||||
}
|
||||
@SerialVersionUID(450698729458760556L)
|
||||
object CoordinateSystem extends EnumFunc {
|
||||
type CoordinateSystem = Value
|
||||
val WGS84 = Value(0) // GPS 坐标系
|
||||
val GCJ02 = Value(1) // 国测局坐标系(火星坐标系)
|
||||
val BD09 = Value(2) // 百度坐标系
|
||||
val BJ54 = Value(3) // 北京54坐标系
|
||||
val XIAN80 = Value(4) // 西安80坐标系
|
||||
val CGCS2000 = Value(5) // 2000国家大地坐标系
|
||||
val XYZ = Value(6) // 笛卡尔坐标系
|
||||
val MERCATOR = Value(7) // 墨卡托坐标系
|
||||
}
|
||||
|
||||
@SerialVersionUID(638715523836978671L)
|
||||
object Azimuth extends EnumFunc {
|
||||
type Azimuth = Value
|
||||
val North = Value(0)
|
||||
val NorthEast = Value(45)
|
||||
val East = Value(90)
|
||||
val SouthEast = Value(135)
|
||||
val South = Value(180)
|
||||
val SouthWest = Value(225)
|
||||
val West = Value(270)
|
||||
val NorthWest = Value(315)
|
||||
}
|
||||
@SerialVersionUID(-713774920813648035L)
|
||||
object DistrictLevel extends EnumFunc {
|
||||
type DistrictLevel = Value
|
||||
/**国家**/
|
||||
val Country = Value("country")
|
||||
/**省,自治区**/
|
||||
val Province = Value("province")
|
||||
/**地级市**/
|
||||
val City = Value("city")
|
||||
/**区,县,县级市**/
|
||||
val District = Value("district")
|
||||
/**街道**/
|
||||
val Street = Value("street")
|
||||
}
|
||||
|
||||
@SerialVersionUID(568714356783645678L)
|
||||
object AdminLevel {
|
||||
/**海外**/
|
||||
val Oversea = "oversea"
|
||||
/**国家**/
|
||||
val Country = "country"
|
||||
/**省,自治区**/
|
||||
val Province = "province"
|
||||
/**地级市**/
|
||||
val City = "city"
|
||||
/**省辖市(属县级市) see:https://baike.baidu.com/item/省直辖县级行政单位**/
|
||||
val ProvincialCity = "provincialcity"
|
||||
/**区,县,县级市**/
|
||||
val District = "district"
|
||||
/**街道**/
|
||||
val Street = "street"
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
import com.google.common.geometry.{S2CellId, S2LatLng}
|
||||
|
||||
@SerialVersionUID(678776497797479999L)
|
||||
case class Location(var lng: Double, var lat: Double) extends Serializable {
|
||||
def id: Long = S2CellId.fromLatLng(S2LatLng.fromDegrees(lat, lng)).id
|
||||
def parendId(level:Int): Long = S2CellId.fromLatLng(S2LatLng.fromDegrees(lat, lng)).parent(level).id
|
||||
}
|
||||
|
||||
object Location {
|
||||
private def verifyLngLat(lng: Double, lat: Double) :Boolean = lng >= -180.0 && lng <= 180.0 && lat >= -90.0 && lat <= 90.0
|
||||
|
||||
def fromLngLatString(strLngLat: String) :Location = {
|
||||
if(null != strLngLat && strLngLat.contains(",")) {
|
||||
val Array(lng, lat) = strLngLat.split(",").map(x=>x.toDouble)
|
||||
|
||||
if(verifyLngLat(lng, lat)) {
|
||||
Location(lng, lat)
|
||||
} else null
|
||||
} else null
|
||||
}
|
||||
|
||||
def fromLatLngString(strLatLng: String) :Location = {
|
||||
if(null != strLatLng && strLatLng.contains(",")) {
|
||||
val Array(lat, lng) = strLatLng.split(",").map(x=>x.toDouble)
|
||||
|
||||
if(verifyLngLat(lng, lat)) {
|
||||
Location(lng, lat)
|
||||
} else null
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
case class Bound(min:Location, max: Location) {
|
||||
def contains(lng:Double, lat:Double) :Boolean =
|
||||
lng >= min.lng && lat >= min.lat && lng <= max.lng && lat <= max.lat
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.entity
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.DistrictLevel.DistrictLevel
|
||||
import com.dengxq.lnglat2Geo.utils.GeoUtils
|
||||
|
||||
case class StreetNode(citycode: Int, adcode: Int, name: String, level: DistrictLevel, center: Location) extends Serializable
|
||||
|
||||
case class StreetMap(data: Map[Int, Array[StreetNode]])
|
||||
|
||||
object Func {
|
||||
implicit def fun(streetMap: StreetMap): _sm = new _sm(streetMap)
|
||||
class _sm(streetMap: StreetMap) {
|
||||
def findNearStreet(districtAdmin: Admin, lonlat: (Double, Double)) :Option[StreetNode] = {
|
||||
val loc = Location(lonlat._1, lonlat._2)
|
||||
val districtId = if(districtAdmin.districtCode != -1) districtAdmin.districtCode else districtAdmin.cityCode
|
||||
if(streetMap.data.contains(districtId)) {
|
||||
val streetDistanceLs = streetMap.data(districtId)
|
||||
.flatMap { sn =>
|
||||
val d = GeoUtils.distance(sn.center, loc)
|
||||
Some(d, sn)
|
||||
}
|
||||
if(streetDistanceLs.nonEmpty) {
|
||||
Some(streetDistanceLs.minBy(_._1)._2)
|
||||
} else None
|
||||
} else None
|
||||
}
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.utils
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.DistrictLevel
|
||||
import com.dengxq.lnglat2Geo.entity.DistrictLevel.DistrictLevel
|
||||
|
||||
object AdminUtils {
|
||||
private final val NATIONS = "阿昌族,鄂温克族,傈僳族,水族,白族,高山族,珞巴族,塔吉克族,保安族,仡佬族,满族,塔塔尔族,布朗族,哈尼族,毛南族,土家族,布依族,哈萨克族,门巴族,土族,朝鲜族,汉族,蒙古族,佤族,达斡尔族,赫哲族,苗族,维吾尔族,傣族,回族,仫佬族,乌孜别克族,德昂族,基诺族,纳西族,锡伯族,东乡族,京族,怒族,瑶族,侗族,景颇族,普米族,彝族,独龙族,柯尔克孜族,羌族,裕固族,俄罗斯族,拉祜族,撒拉族,藏族,鄂伦春族,黎族,畲族,壮族".split(",")
|
||||
|
||||
private final val p1 = """(.+)(?:省|市)$""".r
|
||||
private final val p2 = """(.+)自治区""".r
|
||||
private final val p3 = """(.+)特别行政区""".r
|
||||
|
||||
private final val c0 = """^(.{2})$""".r // 2 长度为2的 "东区" "南区"
|
||||
private final val c1 = """(.+)(?:自治州|自治县)$""".r // 30 自治州 琼中黎族苗族自治县
|
||||
private final val c2 = """(.+)[市|盟|州]$""".r // 304 地级市, 盟; + 1恩施州
|
||||
private final val c3 = """(.+)地区$""".r // 8 地区
|
||||
private final val c4 = """(.+)(?:群岛|填海区)$""".r // 2 东沙群岛
|
||||
private final val c5 = """(.+[^地郊城堂])区$""".r // 20 港澳 不含"东区" "南区"2个字的
|
||||
private final val c6 = """(.+)(?:城区|郊县)$""".r // 6 九龙城区,上海城区,天津城区,北京城区,重庆城区,重庆郊县
|
||||
private final val c7 = """(.+[^郊])县$""".r // 12 台湾的xx县
|
||||
|
||||
private final val d0 = """^(.{2})$""".r // 2 长度为2的 "随县"
|
||||
private final val d1 = """(.+)[市]$""".r // 304 城区 “赤水市”
|
||||
private final val d2 = """(.+)自治县$""".r // 30 自治县
|
||||
private final val d3 = """(.+)自治州直辖$""".r // 30 自治州直辖 "海西蒙古族藏族自治州直辖"
|
||||
private final val d4 = """(.+)[区|县]$""".r // 8 区县
|
||||
private final val d5 = """(.+)(?:乡|镇|街道)$""".r // 8 乡镇|街道
|
||||
|
||||
private final val s0 = """^(.{2})$""".r
|
||||
private final val s1 = """(.+)(?:特别行政管理区|街道办事处|旅游经济特区|民族乡|地区街道)$""".r
|
||||
private final val s2 = """(.+)(?:镇|乡|村|街道|苏木|老街|管理区|区公所|苏木|办事处|社区|经济特区|行政管理区)$""".r
|
||||
|
||||
def shortAdmin(name:String, level:DistrictLevel): String = {
|
||||
level match {
|
||||
case DistrictLevel.Province => shortProvince(name)
|
||||
case DistrictLevel.City => shortCity(name)
|
||||
case DistrictLevel.District => shortDistrict(name)
|
||||
case DistrictLevel.Street => shortStreet(name)
|
||||
case _ => name
|
||||
}
|
||||
}
|
||||
|
||||
def shortProvince(province: String) :String = {
|
||||
province match {
|
||||
case p1(x) => x
|
||||
case p2(x) => if(x== "内蒙古") x else replaceNations(x)
|
||||
case p3(x) => x
|
||||
case _ => province
|
||||
}
|
||||
}
|
||||
|
||||
private def replaceNations(ncity:String) = (Array(ncity) ++ NATIONS).reduce((x, y) => x.replaceAll(y, "").replaceAll(if(y.length > 2) y.replaceAll("族", "") else "", ""))
|
||||
|
||||
private def replaceNationsNotEmpty(name: String) =
|
||||
(Array(name) ++ NATIONS).reduce((x, y) => {
|
||||
val x2 = x.replaceAll(y, "").replaceAll(if (y.length > 2) y.replaceAll("族", "") else "", "")
|
||||
if(x2.isEmpty) x else x2
|
||||
})
|
||||
private[lnglat2Geo] def shortCityImp(city: String) :(String, Int) = {
|
||||
// 总数 383
|
||||
city match {
|
||||
case c0(x) => (x, 0)
|
||||
case c1(x) => (replaceNations(x), 2)
|
||||
case c2(x) => if(x == "襄樊") ("襄阳",1) else (x, 1)
|
||||
case c3(x) => (x,3)
|
||||
case c4(x) => (x,4)
|
||||
case c5(x) => (x,5)
|
||||
case c6(x) => (x,6)
|
||||
case c7(x) => (x,7)
|
||||
case _ => (city, -1)
|
||||
}
|
||||
}
|
||||
|
||||
private[lnglat2Geo] def shortDistrictImp(district: String) :(String, Int) = {
|
||||
// 总数 2963 56个内蒙八旗和新疆兵团没有处理
|
||||
district match {
|
||||
case d0(x) => (x, 0)
|
||||
case d1(x) => (x, 1)
|
||||
case d2(x) => (replaceNations(x), 2)
|
||||
case d3(x) => (replaceNations(x), 3)
|
||||
case d4(x) => (x,4)
|
||||
case d5(x) => (x,5)
|
||||
case _ => (district, -1)
|
||||
}
|
||||
}
|
||||
|
||||
private[lnglat2Geo] def shortStreetImp(street: String) :(String, Int) = {
|
||||
// 总数 42387
|
||||
// 柘城县邵园乡人民政府, 保安镇, 鹅湖镇人民政府, 东风地区
|
||||
street match {
|
||||
case s0(x) => (x, 0)
|
||||
case s1(x) => (replaceNationsNotEmpty(x), 1)
|
||||
case s2(x) => (replaceNationsNotEmpty(x), 2)
|
||||
case _ => (street, -1)
|
||||
}
|
||||
}
|
||||
|
||||
def shortCity(city: String):String = shortCityImp(city)._1
|
||||
|
||||
def shortDistrict(district: String):String = shortDistrictImp(district)._1
|
||||
|
||||
def shortStreet(street: String):String = shortStreetImp(street)._1
|
||||
}
|
@ -1,263 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.utils
|
||||
|
||||
import java.util
|
||||
|
||||
import com.google.common.geometry._
|
||||
import com.dengxq.lnglat2Geo.entity.Azimuth.Azimuth
|
||||
import com.dengxq.lnglat2Geo.entity.{Azimuth, Bound, CoordinateSystem, Location}
|
||||
import com.dengxq.lnglat2Geo.entity.EnumImplicit._
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
object GeoUtils {
|
||||
private val x_PI = Math.PI * 3000.0 / 180.0
|
||||
private val EE = 0.00669342162296594323
|
||||
private val A = 6378245.0 // BJZ54坐标系地球长半轴, m
|
||||
private val EQUATOR_C = 20037508.3427892 // 赤道周长, m
|
||||
private val EARTH_RADIUS = 6378137.0 //WGS84, CGCS2000坐标系地球长半轴, m
|
||||
private val EARTH_POLAR_RADIUS = 6356725.0; //极半径, m
|
||||
|
||||
private val SQRT2 = 1.414213562
|
||||
|
||||
private def rad(d: Double) = d * Math.PI / 180.0
|
||||
|
||||
def distance(locA: Location, locB: Location): Double = {
|
||||
val lngA = locA.lng
|
||||
val latA = locA.lat
|
||||
val lngB = locB.lng
|
||||
val latB = locB.lat
|
||||
val f = rad((latA + latB) / 2)
|
||||
val g = rad((latA - latB) / 2)
|
||||
val l = rad((lngA - lngB) / 2)
|
||||
if (g == 0 && l == 0) return 0
|
||||
var sg = Math.sin(g)
|
||||
var sl = Math.sin(l)
|
||||
var sf = Math.sin(f)
|
||||
var s = .0
|
||||
var c = .0
|
||||
var w = .0
|
||||
var r = .0
|
||||
var d = .0
|
||||
var h1 = .0
|
||||
var h2 = .0
|
||||
var dis = .0
|
||||
val a = EARTH_RADIUS
|
||||
val fl = 1 / 298.257
|
||||
sg = sg * sg
|
||||
sl = sl * sl
|
||||
sf = sf * sf
|
||||
s = sg * (1 - sl) + (1 - sf) * sl
|
||||
c = (1 - sg) * (1 - sl) + sf * sl
|
||||
w = Math.atan(Math.sqrt(s / c))
|
||||
r = Math.sqrt(s * c) / w
|
||||
d = 2 * w * a
|
||||
h1 = (3 * r - 1) / 2 / c
|
||||
h2 = (3 * r + 1) / 2 / s
|
||||
dis = d * (1 + fl * (h1 * sf * (1 - sg) - h2 * (1 - sf) * sg))
|
||||
|
||||
dis.formatted("%.2f").toDouble
|
||||
}
|
||||
|
||||
def gcj02ToWgs84(lng: Double, lat: Double): (Double, Double) = {
|
||||
if (outOfChina(lng, lat)) return new Tuple2[Double, Double](lng, lat)
|
||||
var dlat = transformLat(lng - 105.0, lat - 35.0)
|
||||
var dlng = transformLng(lng - 105.0, lat - 35.0)
|
||||
val radlat = lat / 180.0 * Math.PI
|
||||
var magic = Math.sin(radlat)
|
||||
magic = 1 - EE * magic * magic
|
||||
val sqrtmagic = Math.sqrt(magic)
|
||||
dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * Math.PI)
|
||||
dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * Math.PI)
|
||||
val mglat = lat + dlat
|
||||
val mglng = lng + dlng
|
||||
new Tuple2[Double, Double](lng * 2 - mglng, lat * 2 - mglat)
|
||||
}
|
||||
|
||||
def gcj02ToBD09(lng: Double, lat: Double): (Double, Double) = {
|
||||
if (outOfChina(lng, lat)) return new Tuple2[Double, Double](lng, lat)
|
||||
val z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI)
|
||||
val theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI)
|
||||
val bd_lng = z * Math.cos(theta) + 0.0065
|
||||
val bd_lat = z * Math.sin(theta) + 0.006
|
||||
new Tuple2[Double, Double](bd_lng, bd_lat)
|
||||
}
|
||||
|
||||
def bd09ToGCJ02(lng: Double, lat: Double): (Double, Double) = {
|
||||
if (outOfChina(lng, lat)) return new Tuple2[Double, Double](lng, lat)
|
||||
val x = lng - 0.0065
|
||||
val y = lat - 0.006
|
||||
val z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI)
|
||||
val theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI)
|
||||
val gg_lng = z * Math.cos(theta)
|
||||
val gg_lat = z * Math.sin(theta)
|
||||
new Tuple2[Double, Double](gg_lng, gg_lat)
|
||||
}
|
||||
|
||||
def wgs84ToGCj02(lng: Double, lat: Double): (Double, Double) = {
|
||||
var mglat = .0
|
||||
var mglng = .0
|
||||
if (outOfChina(lng, lat)) {
|
||||
mglat = lat
|
||||
mglng = lng
|
||||
}
|
||||
else {
|
||||
var dLat = transformLat(lng - 105.0, lat - 35.0)
|
||||
var dLon = transformLng(lng - 105.0, lat - 35.0)
|
||||
val radLat = lat / 180.0 * Math.PI
|
||||
var magic = Math.sin(radLat)
|
||||
magic = 1 - EE * magic * magic
|
||||
val sqrtMagic = Math.sqrt(magic)
|
||||
dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI)
|
||||
dLon = (dLon * 180.0) / (A / sqrtMagic * Math.cos(radLat) * Math.PI)
|
||||
mglat = lat + dLat
|
||||
mglng = lng + dLon
|
||||
}
|
||||
new Tuple2[Double, Double](mglng, mglat)
|
||||
}
|
||||
|
||||
private def transformLng(lng: Double, lat: Double) = {
|
||||
var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng))
|
||||
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0
|
||||
ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 * Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0
|
||||
ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 * Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0
|
||||
ret
|
||||
}
|
||||
|
||||
private def transformLat(lng: Double, lat: Double) = {
|
||||
var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng))
|
||||
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0
|
||||
ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0
|
||||
ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 * Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0
|
||||
ret
|
||||
}
|
||||
|
||||
def isInChina(lng: Double, lat: Double): Boolean = !outOfChina(lng, lat)
|
||||
|
||||
def outOfChina(lng: Double, lat: Double): Boolean = {
|
||||
lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271
|
||||
}
|
||||
|
||||
def checkLoc(lng: Double, lat: Double) : Boolean = lng <= 180 && lng >= -180 && lat <=90 && lat >= -90
|
||||
|
||||
def toGCJ02(lng: Double, lat:Double, coordType: CoordinateSystem.CoordinateSystem): (Double, Double) = {
|
||||
coordType match {
|
||||
case CoordinateSystem.WGS84 =>
|
||||
val d = GeoUtils.wgs84ToGCj02(lng, lat)
|
||||
(d._1, d._2)
|
||||
case CoordinateSystem.BD09 =>
|
||||
val d = GeoUtils.bd09ToGCJ02(lng, lat)
|
||||
(d._1, d._2)
|
||||
case _ => (lng, lat)
|
||||
}
|
||||
}
|
||||
|
||||
def toWGS84(lng: Double, lat:Double, coordType: CoordinateSystem.CoordinateSystem): (Double, Double) = {
|
||||
coordType match {
|
||||
case CoordinateSystem.GCJ02 =>
|
||||
val d = GeoUtils.gcj02ToWgs84(lng, lat)
|
||||
(d._1, d._2)
|
||||
case CoordinateSystem.BD09 =>
|
||||
val d02 = GeoUtils.bd09ToGCJ02(lng, lat)
|
||||
val d = GeoUtils.gcj02ToWgs84(d02._1, d02._2)
|
||||
(d._1, d._2)
|
||||
case _ => (lng, lat)
|
||||
}
|
||||
}
|
||||
|
||||
@deprecated
|
||||
def nearest[A](source: Array[A], loc: Location, locExpression: A => Location) :Option[A] = {
|
||||
if(source == null || source.isEmpty) {
|
||||
None
|
||||
} else {
|
||||
val locId = S2CellId.fromLatLng(S2LatLng.fromDegrees(loc.lat, loc.lng)).id
|
||||
val MARK_ITEM = (locId, None)
|
||||
// (source.map {item=>
|
||||
// val itemLoc = locExpression(item)
|
||||
// val id = S2CellId.fromLatLng(S2LatLng.fromDegrees(itemLoc.lat, itemLoc.lng)).id
|
||||
// (GeoUtils.distance(itemLoc, loc), item, id, id.toBinaryString)
|
||||
// } :+ (0, None, locId, locId.toBinaryString))
|
||||
// .sortBy(_._4).foreach(println)
|
||||
|
||||
val sortedLs = (source.map{ item=>
|
||||
val itemLoc = locExpression(item)
|
||||
val id = S2CellId.fromLatLng(S2LatLng.fromDegrees(itemLoc.lat, itemLoc.lng)).id
|
||||
(id, Some(item))
|
||||
} :+ MARK_ITEM).sortBy(_._1)
|
||||
val locIndex = sortedLs.indexOf(MARK_ITEM)
|
||||
val nearestLeft: Option[A] = if(locIndex != 0) sortedLs(locIndex - 1)._2 else None
|
||||
val nearestRight: Option[A] = if(locIndex == sortedLs.length - 1) None else sortedLs(locIndex + 1)._2
|
||||
|
||||
val distanceLs : Array[(Double, A)] = Array(nearestLeft, nearestRight).flatMap {
|
||||
case Some(nearest) =>
|
||||
val d = GeoUtils.distance(locExpression(nearest), loc)
|
||||
Some(d, nearest)
|
||||
case None => None
|
||||
}
|
||||
|
||||
if(distanceLs.nonEmpty) {
|
||||
Some(distanceLs.minBy(_._1)._2)
|
||||
} else None
|
||||
}
|
||||
}
|
||||
|
||||
private val kEarthCircumferenceMeters = 1000 * 40075.017
|
||||
private def earthMeters2Radians(meters: Double) = (2 * S2.M_PI) * (meters / kEarthCircumferenceMeters)
|
||||
|
||||
def genS2Cap(loc: Location, radius: Double): S2Cap = {
|
||||
val s2LatLng = S2LatLng.fromDegrees(loc.lat, loc.lng)
|
||||
val radiusRadians = earthMeters2Radians(radius)
|
||||
S2Cap.fromAxisHeight(s2LatLng.normalized.toPoint, (radiusRadians * radiusRadians) / 2)
|
||||
}
|
||||
|
||||
def genS2CellUnion[T<: S2Region](regions: Seq[T], minLevel:Int = 0, maxLevel:Int = 0, maxCells:Int=0) :S2CellUnion = {
|
||||
val regionCoverer = new S2RegionCoverer()
|
||||
if(maxCells!=0) regionCoverer.setMaxCells(maxCells)
|
||||
if(maxLevel!=0) regionCoverer.setMaxLevel(maxLevel)
|
||||
if(minLevel!=0) regionCoverer.setMinLevel(minLevel)
|
||||
val s2CellIds = new util.ArrayList[S2CellId]()
|
||||
regions.flatMap(x=>regionCoverer.getCovering(x).cellIds().asScala)
|
||||
.foreach(s2CellIds.add)
|
||||
val cellUnion = new S2CellUnion()
|
||||
cellUnion.initFromCellIds(s2CellIds)
|
||||
cellUnion
|
||||
}
|
||||
|
||||
def move(loc:Location, distance:Double, azimuth: Azimuth) :Location = {
|
||||
val radLat = rad(loc.lat)
|
||||
val radLng = rad(loc.lng)
|
||||
|
||||
val ec = EARTH_POLAR_RADIUS + (EARTH_RADIUS - EARTH_POLAR_RADIUS) * (90 - loc.lng) / 90
|
||||
val ed = ec * Math.cos(radLat)
|
||||
|
||||
val dx = distance * Math.sin(azimuth * Math.PI / 180)
|
||||
val dy = distance * Math.cos(azimuth * Math.PI / 180)
|
||||
|
||||
val lng = (dx / ed + radLng) * 180 / Math.PI
|
||||
val lat = (dy / ec + radLat) * 180 / Math.PI
|
||||
|
||||
Location(lng, lat)
|
||||
}
|
||||
|
||||
def genCapBound(loc:Location, radius:Double) :Bound= {
|
||||
val swDistance = SQRT2 * radius
|
||||
val sw = move(loc, swDistance, Azimuth.SouthWest)
|
||||
val ne = move(loc, swDistance, Azimuth.NorthEast)
|
||||
Bound(sw, ne)
|
||||
}
|
||||
|
||||
def genCapInnerBound(loc:Location, radius:Double) :Bound= {
|
||||
// val swDistance = SQRT2 / 2d * radius
|
||||
val sw = move(loc, radius, Azimuth.SouthWest)
|
||||
val ne = move(loc, radius, Azimuth.NorthEast)
|
||||
Bound(sw, ne)
|
||||
}
|
||||
|
||||
def genCellBound(center: Location, radius: Double, level: Int): Seq[String] = {
|
||||
val cap = genS2Cap(center, radius)
|
||||
val cu = genS2CellUnion(Seq(cap), level, level, 5)
|
||||
cu.cellIds().asScala.map{_.id.toHexString}
|
||||
}
|
||||
|
||||
def toCellBoundId(lng:Double, lat:Double, level:Int): String =
|
||||
S2CellId.fromLatLng(S2LatLng.fromDegrees(lat, lng)).parent(9).id.toHexString
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.utils
|
||||
|
||||
/**
|
||||
* Created by dengxq on 18-8-3.
|
||||
*/
|
||||
object LineUtils {
|
||||
|
||||
/**
|
||||
* 已知两点,求两点直线
|
||||
*
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @return 直线
|
||||
*/
|
||||
def getLine(p1: Point, p2: Point): FitLine = {
|
||||
val k = (p2.y - p1.y) / (p2.x - p1.x)
|
||||
val b = p1.y - k * p1.x
|
||||
FitLine(k, b)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两条直线的交点, 直线为 y = kx + b 型直线
|
||||
*
|
||||
* @param fitLine 第一条直线
|
||||
* @param fitLine2 第二条直线
|
||||
* @return 交点
|
||||
*/
|
||||
def getLineIntersection(fitLine: FitLine, fitLine2: FitLine): (Double, Double) = {
|
||||
val x = -(fitLine.b - fitLine2.b) / (fitLine.k - fitLine2.k)
|
||||
val y = fitLine.k * x + fitLine.b
|
||||
(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* 点到直线的距离,直线为 y = kx + b 型直线
|
||||
*
|
||||
* @param fitLine
|
||||
* @param p
|
||||
* @return
|
||||
*/
|
||||
def pointToLineDistance(fitLine: FitLine, p: Point): Double = {
|
||||
val dis = Math.abs(fitLine.k * p.x - p.y + fitLine.b) / Math.sqrt(fitLine.k * fitLine.k + 1)
|
||||
dis
|
||||
}
|
||||
|
||||
/**
|
||||
* 平行直线之间的距离,直线为 y = kx + b 型直线
|
||||
*
|
||||
* @param fitLine
|
||||
* @param fitLine2
|
||||
* @return
|
||||
*/
|
||||
def lineToLineDistance(fitLine: FitLine, fitLine2: FitLine): Double = {
|
||||
var dis = -1.0
|
||||
if (fitLine.k == fitLine2.k)
|
||||
dis = Math.abs(fitLine.b - fitLine2.b) / Math.sqrt(fitLine.k * fitLine.k + 1)
|
||||
dis
|
||||
}
|
||||
|
||||
/**
|
||||
* 两条直线直线的夹角,tan值,如需具体角度,需做arctan处理
|
||||
*
|
||||
* @param fitLine
|
||||
* @param fitLine2
|
||||
* @return 两条直线直线的夹角的tan值
|
||||
*/
|
||||
def lineToLineAngle(fitLine: FitLine, fitLine2: FitLine): Double = {
|
||||
Math.atan(Math.abs(fitLine.k - fitLine2.k) / (fitLine.k * fitLine2.k + 1))
|
||||
}
|
||||
|
||||
/**
|
||||
* 点在直线上的投影
|
||||
*
|
||||
* @param fitLine 直线
|
||||
* @param p 点
|
||||
* @return 投影点
|
||||
*/
|
||||
// def projectionOnLine(fitLine: FitLine, p: MPoint): (Double, Double) = {
|
||||
// val xx = (fitLine.k * p.y - fitLine.k * fitLine.b + p.x) / (fitLine.k * fitLine.k + 1)
|
||||
// val yy = (fitLine.k * fitLine.k * p.y + fitLine.k * p.x + fitLine.b) / (fitLine.k * fitLine.k + 1)
|
||||
// (xx, yy)
|
||||
// }
|
||||
|
||||
def projectionOnLine(fitLine: FitLine, p: Point): (Double, Double) = {
|
||||
val xx = (fitLine.k * p.y - fitLine.k * fitLine.b + p.x) / (fitLine.k * fitLine.k + 1)
|
||||
val yy = (fitLine.k * fitLine.k * p.y + fitLine.k * p.x + fitLine.b) / (fitLine.k * fitLine.k + 1)
|
||||
(xx, yy)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两条线段是否相交
|
||||
* @param line1
|
||||
* @param line2
|
||||
* @return
|
||||
*/
|
||||
def isLineSegmentIntersecting(line1: LineSegment, line2: LineSegment): Boolean = {
|
||||
val a = line1.p1
|
||||
val b = line1.p2
|
||||
val c = line2.p1
|
||||
val d = line2.p2
|
||||
|
||||
// 快速排斥:
|
||||
// 两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的
|
||||
// 1.线段ab的低点低于cd的最高点(可能重合)
|
||||
// 2.cd的最左端小于ab的最右端(可能重合)
|
||||
// 3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合)
|
||||
// 4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合)
|
||||
// 综上4个条件,两条线段组成的矩形是重合的,特别要注意一个矩形含于另一个矩形之内的情况
|
||||
if (!(Math.min(a.x, b.x) <= Math.max(c.x, d.x) && Math.min(c.y, d.y) <= Math.max(a.y, b.y)
|
||||
&& Math.min(c.x, d.x) <= Math.max(a.x, b.x) && Math.min(a.y, b.y) <= Math.max(c.y, d.y)))
|
||||
return false
|
||||
|
||||
// 跨立实验:
|
||||
// 如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段
|
||||
// 也就是说a b两点在线段cd的两端,c d两点在线段ab的两端
|
||||
val u = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y)
|
||||
val v = (d.x - a.x) * (b.y - a.y) - (b.x - a.x) * (d.y - a.y)
|
||||
val w = (a.x - c.x) * (d.y - c.y) - (d.x - c.x) * (a.y - c.y)
|
||||
val z = (b.x - c.x) * (d.y - c.y) - (d.x - c.x) * (b.y - c.y)
|
||||
u * v <= 0.00000001 && w * z <= 0.00000001
|
||||
}
|
||||
|
||||
/**
|
||||
* 点到线段的距离
|
||||
* @param x1
|
||||
* @param y1
|
||||
* @param x2
|
||||
* @param y2
|
||||
* @param x0
|
||||
* @param y0
|
||||
* @return
|
||||
*/
|
||||
def pointToLineDis(x1: Double, y1: Double, x2: Double, y2: Double, x0: Double, y0: Double): Double = {
|
||||
var a = .0
|
||||
var b = .0
|
||||
var c = .0
|
||||
a = lineDis(x1, y1, x2, y2) // 线段的长度
|
||||
b = lineDis(x1, y1, x0, y0) // 点到起点的距离
|
||||
c = lineDis(x2, y2, x0, y0) // 点到终点的距离
|
||||
//点在端点上
|
||||
if (c <= 0.000001 || b <= 0.000001) {
|
||||
return 0
|
||||
}
|
||||
//直线距离过短
|
||||
if (a <= 0.000001) {
|
||||
return b
|
||||
}
|
||||
// 点在起点左侧,距离等于点到起点距离
|
||||
if (c * c >= a * a + b * b) {
|
||||
return b
|
||||
}
|
||||
//点在终点右侧,距离等于点到终点距离
|
||||
if (b * b >= a * a + c * c) {
|
||||
return c
|
||||
}
|
||||
//点在起点和终点中间,为垂线距离
|
||||
val k = (y2 - y1) / (x2 - x1)
|
||||
val z = y1 - k * x1
|
||||
val p = (a + b + c) / 2
|
||||
// 半周长
|
||||
val s = Math.sqrt(p * (p - a) * (p - b) * (p - c)) //海伦公式求面积
|
||||
2 * s / a // 返回点到线的距离(利用三角形面积公式求高)
|
||||
}
|
||||
|
||||
// 计算两点之间的距离
|
||||
def lineDis(x1: Double, y1: Double, x2: Double, y2: Double): Double =
|
||||
Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
|
||||
|
||||
case class Point(x: Double,
|
||||
y: Double
|
||||
)
|
||||
|
||||
case class LineSegment(p1: Point,
|
||||
p2: Point
|
||||
)
|
||||
|
||||
case class FitLine(k: Double,
|
||||
b: Double
|
||||
)
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.utils
|
||||
|
||||
import java.io._
|
||||
|
||||
object ObjectSerializer {
|
||||
def serialize[T](o: T, fname: String) {
|
||||
val bos = new FileOutputStream(fname)//基于磁盘文件流的序列化
|
||||
val oos = new ObjectOutputStream(bos)
|
||||
oos.writeObject(o)
|
||||
oos.close()
|
||||
}
|
||||
|
||||
def deserialize[T](fname: String): T = {
|
||||
val bis = new FileInputStream(fname)
|
||||
val ois = new ObjectInputStream(bis)
|
||||
ois.readObject.asInstanceOf[T]
|
||||
}
|
||||
|
||||
def deserialize[T](bis: InputStream): T = {
|
||||
val ois = new ObjectInputStream(bis)
|
||||
ois.readObject.asInstanceOf[T]
|
||||
}
|
||||
|
||||
def serialize[T](o: T): Array[Byte] = {
|
||||
val bos = new ByteArrayOutputStream()
|
||||
val oos = new ObjectOutputStream(bos)
|
||||
oos.writeObject(o)
|
||||
oos.close()
|
||||
bos.toByteArray
|
||||
}
|
||||
|
||||
def deserialize[T](bytes: Array[Byte]): T={
|
||||
val bis=new ByteArrayInputStream(bytes)
|
||||
val ois=new ObjectInputStream(bis)
|
||||
ois.readObject.asInstanceOf[T]
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo.utils
|
||||
|
||||
import com.google.common.geometry._
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
object S2Utils {
|
||||
|
||||
// 预算,提升速度
|
||||
val capHeightMap = List(2, 4, 8, 16, 32, 64, 128, 256).map(radius => {
|
||||
val rad = earthMeters2Radians(radius * 1000)
|
||||
(radius * 1000, rad * rad * 2)
|
||||
}).toMap
|
||||
|
||||
def getCellId(s2LatLng: S2LatLng, radius: Int, desLevel: Int): List[Long] = {
|
||||
val capHeight = capHeightMap.getOrElse(radius, 0d)
|
||||
val cap = S2Cap.fromAxisHeight(s2LatLng.toPoint, capHeight)
|
||||
val coverer = new S2RegionCoverer
|
||||
coverer.setMaxLevel(desLevel)
|
||||
coverer.setMinLevel(desLevel)
|
||||
//圆形内的cell会自动做聚合,手动拆分
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
coverer.getCovering(cap)
|
||||
.cellIds
|
||||
.flatMap(s2CellId => {
|
||||
val cellLevel = getLevel(s2CellId.id)
|
||||
if (cellLevel == desLevel) List(s2CellId.id())
|
||||
else childrenCellId(s2CellId, cellLevel, desLevel).map(_.id())
|
||||
})
|
||||
.toList
|
||||
}
|
||||
|
||||
def childrenCellId(s2CellId: S2CellId, curLevel: Int, desLevel: Int): ArrayBuffer[S2CellId] = {
|
||||
val list = ArrayBuffer.empty[S2CellId]
|
||||
if (curLevel < desLevel) {
|
||||
val interval = (s2CellId.childEnd.id - s2CellId.childBegin.id) / 4
|
||||
(0 until 4).foreach(i => {
|
||||
val id = s2CellId.childBegin.id + interval * i
|
||||
val cellId = new S2CellId(id)
|
||||
list ++= childrenCellId(cellId, curLevel + 1, desLevel)
|
||||
})
|
||||
} else list.append(s2CellId)
|
||||
list
|
||||
}
|
||||
|
||||
def getLevel(inputs: Long): Int = {
|
||||
var n = 0
|
||||
var input = inputs
|
||||
while (input % 2 == 0) {
|
||||
input = input / 2
|
||||
n += 1
|
||||
}
|
||||
30 - n / 2
|
||||
}
|
||||
|
||||
private val kEarthCircumferenceMeters = 1000 * 40075.017
|
||||
|
||||
private def earthMeters2Radians(meters: Double) :Double = (2 * S2.M_PI) * (meters / 40075017)
|
||||
|
||||
def loadS2CellUnion(polylineString: String, minLevel: Int = -1, maxLevel: Int = -1, maxCells: Int = -1, isInner: Boolean = false): S2CellUnion = {
|
||||
val s2PolygonBuilder = new S2PolygonBuilder()
|
||||
|
||||
// polyline 中 | 分割多个polygon, 每个polygon的点用 ; 分割.
|
||||
// 每个 polygon 构建 一个 S2Loop, 合并到 S2PolygonBuilder中生成 multi polygon
|
||||
polylineString.split('|').foreach(loopStr => {
|
||||
val points: Array[S2Point] = loopStr.split(';').map(coordStr => {
|
||||
val parts = coordStr.split(',')
|
||||
val wgs84 = GeoUtils.gcj02ToWgs84(parts(0).toDouble, parts(1).toDouble)
|
||||
S2LatLng.fromDegrees(wgs84._2, wgs84._1).toPoint
|
||||
})
|
||||
val jPoints = points.toList.asJava
|
||||
|
||||
s2PolygonBuilder.addLoop(new S2Loop(jPoints))
|
||||
})
|
||||
val polygon = s2PolygonBuilder.assemblePolygon()
|
||||
// 栅格化处理区域多边形
|
||||
val coverer = new S2RegionCoverer()
|
||||
if (minLevel > 0) coverer.setMinLevel(minLevel)
|
||||
if (maxLevel > 0) coverer.setMaxLevel(maxLevel)
|
||||
if (maxCells > 0) coverer.setMaxCells(maxCells)
|
||||
val s2CellUnion = if (isInner) coverer.getInteriorCovering(polygon) else coverer.getCovering(polygon)
|
||||
s2CellUnion
|
||||
}
|
||||
|
||||
def loadS2CellUnionFromId(polyline: Array[Array[Long]], minLevel: Int = -1, maxLevel: Int = -1, maxCells: Int = -1, isInner: Boolean = false): S2CellUnion = {
|
||||
val s2PolygonBuilder = new S2PolygonBuilder()
|
||||
polyline.foreach(loopStr => {
|
||||
val points: Array[S2Point] = loopStr.map(id => new S2CellId(id).toPoint)
|
||||
val jPoints = points.toList.asJava
|
||||
s2PolygonBuilder.addLoop(new S2Loop(jPoints))
|
||||
})
|
||||
val polygon = s2PolygonBuilder.assemblePolygon()
|
||||
// 栅格化处理区域多边形
|
||||
val coverer = new S2RegionCoverer()
|
||||
if (minLevel > 0) coverer.setMinLevel(minLevel)
|
||||
if (maxLevel > 0) coverer.setMaxLevel(maxLevel)
|
||||
if (maxCells > 0) coverer.setMaxCells(maxCells)
|
||||
coverer.setLevelMod(3)
|
||||
val s2CellUnion = if (isInner) coverer.getInteriorCovering(polygon) else coverer.getCovering(polygon)
|
||||
// s2CellUnion.normalize()
|
||||
s2CellUnion
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FlatSpec
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class AdminDataProviderTest extends FlatSpec {
|
||||
|
||||
"Node data" should "Serialize & Deserialize" in {
|
||||
// Upgrade.upgradeAdminBoundary()
|
||||
// Upgrade.upgradeAdminBoundaryCell()
|
||||
// Upgrade.upgradeTownship()
|
||||
// Upgrade.upgradeChinaAdmin()
|
||||
// Upgrade.upgradeBusinessAreaData()
|
||||
|
||||
GeoTrans.normalizeName("","攀枝花","","")
|
||||
.foreach(println)
|
||||
//adminData.values.filter(s => s.level.equals(DistrictLevel.Province)).filter(s => s.name.equals("香港") || s.shortName.equals("香港"))
|
||||
// .flatMap(s => s.children.map(adCode => adminData.get(adCode).orNull)).foreach(println)
|
||||
GeoTrans.normalizeName("","北京","海淀","")
|
||||
.foreach(println)
|
||||
// val result = GeoSpatial.aroundBusinessAreas(116.36450116862099,39.9260007939375, 4000, CoordinateSystem.WGS84)
|
||||
// println(result)
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,121 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.{Admin, Location}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FlatSpec
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class AdminStreetTest extends FlatSpec {
|
||||
// val chinaCellNode: DistrictNode = AdminArea.loadChina
|
||||
// val chinaStreetMap: StreetMap = AdminArea.loadChinaStreet
|
||||
val testSource = Array(
|
||||
// (112.596628,34.973136), //
|
||||
// (122.376448,29.972796), //
|
||||
// (119.203405,39.374154), //
|
||||
(121.1572265625,23.9260130330), // 中国台湾省南投县仁爱乡
|
||||
(112.567757,35.096176), // 济源
|
||||
(116.9565868378,39.6513677208), // 天津市武清区河西务镇
|
||||
(100.4315185547,21.7594997307), // 中国云南省西双版纳傣族自治州勐海县勐混镇
|
||||
(85.5670166016,41.5548386631), // 中国新疆维吾尔自治区巴音郭楞蒙古自治州库尔勒市 普惠乡
|
||||
(117.9969406128,27.7447712551), // 中国福建省南平市武夷山市 崇安街道
|
||||
(110.8520507813,34.0526594214), // 河南省三门峡市卢氏县 瓦窑沟乡下河村
|
||||
(116.4811706543,39.9255352817), // 北京市朝阳区 六里屯街道甜水园
|
||||
(116.3362348080,40.0622912084), // 北京市昌平区 回龙观地区吉晟别墅社区
|
||||
(116.3362830877,40.0594500522), // 北京市北京市昌平区 建材城西路65号
|
||||
(116.3325601816,40.0397393499), // 北京市海淀区 清河街道 小米六期
|
||||
(117.0977783203,36.5085323575), // 山东省济南市历城区
|
||||
(118.6358642578,35.8356283889), // 山东省临沂市沂水县
|
||||
(119.7853088379,36.3029520437), // 山东省潍坊市高密市柏城镇
|
||||
(119.8567199707,36.2808142593), // 山东省青岛市胶州市胶西镇
|
||||
(120.3892135620,36.2777698228), // 山东省青岛市城阳区流亭街道于家社区
|
||||
(116.3830447197,39.9467430610), // 中国北京市北京市西城区鼓楼西大街171号
|
||||
(116.3854265213,39.9444070723) // 北京市西城区什刹海街道鼓西社区
|
||||
)
|
||||
|
||||
val testResult = Array(
|
||||
Admin.createProvincialCity("台湾省","南投县", 710008,710008, Location(0.0,0.0)),
|
||||
Admin.createProvincialCity("河南省","济源市", 419001, 419001, Location(112.602256,35.067199)),
|
||||
Admin.createDistrict("天津市", "天津城区", "武清区", 120100, 120100, 120114, Location(117.044387,39.384119)),
|
||||
Admin.createDistrict( "云南省", "西双版纳傣族自治州", "勐海县", 532800,532800, 532822, Location(100.452547, 21.957353)),
|
||||
Admin.createDistrict("新疆维吾尔自治区", "巴音郭楞蒙古自治州", "库尔勒市", 652800, 652800, 652801, Location(86.174633,41.725891)),
|
||||
Admin.createDistrict("福建省", "南平市", "武夷山市", 350700, 350700, 350782, Location(118.035309,27.756647)),
|
||||
Admin.createDistrict("河南省", "三门峡市", "卢氏县", 411200, 411200, 411224, Location(111.047858,34.054324)),
|
||||
Admin.createDistrict("北京市", "北京城区", "朝阳区", 110100, 110100, 110105, Location(116.443205,39.921506)),
|
||||
Admin.createDistrict("北京市", "北京城区", "昌平区", 110100, 110100, 110114, Location(116.231254,40.220804)),
|
||||
Admin.createDistrict("北京市", "北京城区", "昌平区", 110100, 110100, 110114, Location(116.231254,40.220804)),
|
||||
Admin.createDistrict("北京市", "北京城区", "海淀区", 110100, 110100, 110108, Location(116.298262,39.95993)),
|
||||
Admin.createDistrict("山东省", "济南市", "历城区", 370100, 370100, 370112, Location(117.06523,36.680259)),
|
||||
Admin.createDistrict("山东省", "临沂市", "沂水县", 371300, 371300, 371323, Location(118.627917,35.79045)),
|
||||
Admin.createDistrict( "山东省", "潍坊市", "高密市", 370700, 370700, 370785, Location(119.755597,36.382594)),
|
||||
Admin.createDistrict("山东省", "青岛市", "胶州市", 370200, 370200, 370281, Location(120.033382,36.26468)),
|
||||
Admin.createDistrict( "山东省", "青岛市", "城阳区", 370200, 370200, 370214, Location(120.396256,36.307559))
|
||||
)
|
||||
|
||||
"AdminStreet.nearestStreet in scala " should "work" in {
|
||||
// for(i <- 0 until Math.min(testSource.length,testResult.length) - 1) {
|
||||
// val lonlat = testSource(i)
|
||||
// val admin = AdminArea.nearestStreet(lonlat, CoordinateSystem.GCJ02, chinaCellNode, chinaStreetMap)
|
||||
// println(admin)
|
||||
// }
|
||||
}
|
||||
|
||||
"AdminStreet.nearestStreet in scala " should "success - qps > 14w" in {
|
||||
|
||||
// val range = 100000
|
||||
// val total = range * testSource.length
|
||||
// var n = 0
|
||||
// val start = new Date().getTime
|
||||
// for(x <- 1 to range) {
|
||||
//
|
||||
// for(i <- testSource.indices) {
|
||||
// val lonlat = testSource(i)
|
||||
//
|
||||
// val _ = AdminArea.nearestStreet(lonlat, CoordinateSystem.GCJ02, chinaCellNode, chinaStreetMap)
|
||||
// n = n + 1
|
||||
// if(n%10000 == 0) {
|
||||
// logger.info(n+" case done")
|
||||
// n = 0
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(n > 0) logger.info(n+" case done")
|
||||
//
|
||||
// val end = new Date().getTime
|
||||
// val elapsed = end - start
|
||||
// val qps = total / (elapsed/1000)
|
||||
// logger.info("done elapsed:" + elapsed + "ms, qps:" + qps + "/s")
|
||||
// assert(qps > 140000, "qps need > 14w")
|
||||
}
|
||||
//
|
||||
// "AdminStreet in scala " should " success" in {
|
||||
// for(i <- 0 until Math.min(testSource.length,testResult.length) - 1) {
|
||||
// val lonlat = testSource(i)
|
||||
// val startLoad = new Date()
|
||||
// val admin = AdminArea.determineStreet(lonlat, CoordinateSystem.GCJ02)
|
||||
// val endLoad = new Date()
|
||||
// logger.info(admin.toJson+"\tCalc time:"+ (endLoad.getTime - startLoad.getTime))
|
||||
// val shouldAdmin = testResult(i)
|
||||
//// assert(shouldAdmin.toJson == admin.toJson)
|
||||
// }
|
||||
// logger.info("done")
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// "AdminStreet in spark " should " success" in {
|
||||
//
|
||||
// val a= sparkContext.parallelize(Array(chinaCellNode))
|
||||
// a.foreach(r=>{
|
||||
// println(r)
|
||||
// })
|
||||
//
|
||||
// val china = sparkContext.broadcast(chinaCellNode)
|
||||
// val chinaStreets = sparkContext.broadcast(chinaStreetMap)
|
||||
// sparkContext.parallelize(testSource).repartition(3).map(lnglat=>{
|
||||
// val admin = AdminArea.determineStreet(lnglat, CoordinateSystem.GCJ02)(china.value,chinaStreets.value)
|
||||
// admin.toJson
|
||||
// }).collect().foreach(println)
|
||||
//
|
||||
// }
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo
|
||||
|
||||
import com.dengxq.lnglat2Geo.entity.{Admin, CoordinateSystem, Location}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FlatSpec
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class AdminTest extends FlatSpec {
|
||||
// val chinaCellNode: DistrictNode = AdminArea.loadChina
|
||||
|
||||
val testSource = Array(
|
||||
(121.1572265625,23.9260130330), // 中国台湾省南投县仁爱乡
|
||||
(112.567757,35.096176), // 济源
|
||||
(116.9565868378,39.6513677208), // 天津市武清区河西务镇
|
||||
(100.4315185547,21.7594997307), // 中国云南省西双版纳傣族自治州勐海县勐混镇
|
||||
(85.5670166016,41.5548386631), // 中国新疆维吾尔自治区巴音郭楞蒙古自治州库尔勒市 普惠乡
|
||||
(117.9969406128,27.7447712551), // 中国福建省南平市武夷山市 崇安街道
|
||||
(110.8520507813,34.0526594214), // 河南省三门峡市卢氏县 瓦窑沟乡下河村
|
||||
(116.4811706543,39.9255352817), // 北京市朝阳区 六里屯街道甜水园
|
||||
(116.3362348080,40.0622912084), // 北京市昌平区 回龙观地区吉晟别墅社区
|
||||
(116.3362830877,40.0594500522), // 北京市北京市昌平区 建材城西路65号
|
||||
(116.3325601816,40.0397393499), // 北京市海淀区 清河街道
|
||||
(117.0977783203,36.5085323575), // 山东省济南市历城区
|
||||
(118.6358642578,35.8356283889), // 山东省临沂市沂水县
|
||||
(119.7853088379,36.3029520437), // 山东省潍坊市高密市柏城镇
|
||||
(119.8567199707,36.2808142593), // 山东省青岛市胶州市胶西镇
|
||||
(120.3892135620,36.2777698228), // 山东省青岛市城阳区流亭街道于家社区
|
||||
(120.152983,36.119759), // 海外
|
||||
(98.774694,23.706633) // 海外
|
||||
// (116.3830447197,39.9467430610), // 中国北京市北京市西城区鼓楼西大街171号
|
||||
// (116.3854265213,39.9444070723) // 北京市西城区什刹海街道鼓西社区
|
||||
// (116.3363742828, 40.0300297342) // 北京市海淀区清河街道 五彩城购物中心东区
|
||||
)
|
||||
|
||||
val testResult = Array(
|
||||
Admin.createProvincialCity("台湾省","南投县", 710008,710008, Location(0.0,0.0)),
|
||||
Admin.createProvincialCity("河南省","济源市", 419001, 419001, Location(112.602256,35.067199)),
|
||||
Admin.createDistrict("天津市", "天津城区", "武清区", 120100, 120100, 120114, Location(117.044387,39.384119)),
|
||||
Admin.createDistrict( "云南省", "西双版纳傣族自治州", "勐海县", 532800,532800, 532822, Location(100.452547, 21.957353)),
|
||||
Admin.createDistrict("新疆维吾尔自治区", "巴音郭楞蒙古自治州", "库尔勒市", 652800, 652800, 652801, Location(86.174633,41.725891)),
|
||||
Admin.createDistrict("福建省", "南平市", "武夷山市", 350700, 350700, 350782, Location(118.035309,27.756647)),
|
||||
Admin.createDistrict("河南省", "三门峡市", "卢氏县", 411200, 411200, 411224, Location(111.047858,34.054324)),
|
||||
Admin.createDistrict("北京市", "北京城区", "朝阳区", 110100, 110100, 110105, Location(116.443205,39.921506)),
|
||||
Admin.createDistrict("北京市", "北京城区", "昌平区", 110100, 110100, 110114, Location(116.231254,40.220804)),
|
||||
Admin.createDistrict("北京市", "北京城区", "昌平区", 110100, 110100, 110114, Location(116.231254,40.220804)),
|
||||
Admin.createDistrict("北京市", "北京城区", "海淀区", 110100, 110100, 110108, Location(116.298262,39.95993)),
|
||||
Admin.createDistrict("山东省", "济南市", "历城区", 370100, 370100, 370112, Location(117.06523,36.680259)),
|
||||
Admin.createDistrict("山东省", "临沂市", "沂水县", 371300, 371300, 371323, Location(118.627917,35.79045)),
|
||||
Admin.createDistrict( "山东省", "潍坊市", "高密市", 370700, 370700, 370785, Location(119.755597,36.382594)),
|
||||
Admin.createDistrict("山东省", "青岛市", "胶州市", 370200, 370200, 370281, Location(120.033382,36.26468)),
|
||||
Admin.createDistrict( "山东省", "青岛市", "城阳区", 370200, 370200, 370214, Location(120.396256,36.307559)),
|
||||
Admin.createDistrict("海外", "海外", "海外", -1, -1, -1, Location(120.396256,36.307559)),
|
||||
Admin.createDistrict("海外", "海外", "海外", -1, -1, -1, Location(120.396256,36.307559))
|
||||
)
|
||||
|
||||
"AdminArea.determineAdmin in scala " should "qps > 14w" in {
|
||||
|
||||
// val range = 10000
|
||||
// val total = range * testSource.length
|
||||
// var n = 0
|
||||
// val start = new Date().getTime
|
||||
// for(x <- 1 to range) {
|
||||
//
|
||||
// for(i <- testSource.indices) {
|
||||
// val lonlat = testSource(i)
|
||||
//
|
||||
// val _ = AdminArea.determineAdmin(lonlat, CoordinateSystem.GCJ02, chinaCellNode)
|
||||
// n = n + 1
|
||||
// if(n%10000 == 0) {
|
||||
// logger.info(n+" case done")
|
||||
// n = 0
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if(n > 0) logger.info(n+" case done")
|
||||
//
|
||||
// val end = new Date().getTime
|
||||
// val elapsed = end - start
|
||||
// val qps = total / (elapsed/1000)
|
||||
// logger.info("done elapsed:" + elapsed + "ms, qps:" + qps + "/s")
|
||||
// assert(qps > 140000, "qps need > 14w")
|
||||
}
|
||||
|
||||
"AdminArea.determineAdmin in spark " should "work" in {
|
||||
|
||||
// val a= sparkContext.parallelize(Array(chinaCellNode))
|
||||
// a.foreach(r=>{
|
||||
// println(r)
|
||||
// })
|
||||
//
|
||||
// val china = sparkContext.broadcast(chinaCellNode)
|
||||
// sparkContext.parallelize(testSource).repartition(3).map(lnglat=>{
|
||||
// val aroundAreas = AdminArea.determineAdmin(lnglat, CoordinateSystem.GCJ02, china.value)
|
||||
// aroundAreas.toJson
|
||||
// }).collect().foreach(println)
|
||||
}
|
||||
|
||||
|
||||
|
||||
"AdminArea.determineAdmin in scala" should "work" in {
|
||||
|
||||
val start3 = System.currentTimeMillis()
|
||||
GeoTrans.init()
|
||||
println("cost1 = " + (System.currentTimeMillis() - start3))
|
||||
|
||||
val start1 = System.currentTimeMillis()
|
||||
println(GeoTrans.determineAdmin(120.152983,36.119759, CoordinateSystem.GCJ02))
|
||||
println("cost2 = " + (System.currentTimeMillis() - start1))
|
||||
|
||||
// val str = "115.068863,30.203944;112.905879,28.281898;122.956308,41.098388; 114.32782,34.787914;113.830148,34.638862;113.078327,28.161037;116.415704,39.953465;120.551632,31.283096;106.114479, 30.82612;111.658452,23.151697;115.642757,34.412346;112.226943,30.365528;117.235243,32.755445;119.652622,29.067311;111.418815,27.676285;113.117786,36.183096; 96.123861,16.835776;116.659674,39.516241;106.068781,30.765933;121.512104,29.897897"
|
||||
|
||||
val str = "112.989382,28.147062;109.046404,35.042294;106.559531,29.607832;119.481842,33.652686;116.525612,39.824004;109.090599,35.080281;113.508112,37.892087;123.417829,41.791227;120.517459,30.459049;113.865295,35.290525;110.290043,20.015764;108.934191,34.294362;117.183897,34.264914;126.587992,45.757869;115.859063,28.695778;106.771075,26.584885; 108.92224,34.233088;113.809742,23.067213;118.778811,32.089465;113.715261,35.2587"
|
||||
str.split(";").foreach(s => {
|
||||
val parts = s.split(",")
|
||||
val ss = GeoTrans.determineAdmin(parts(0).toDouble, parts(1).toDouble, CoordinateSystem.WGS84)
|
||||
println(ss)
|
||||
})
|
||||
// println(AdminArea.determineAdmin((119.52320949527594,34.62947031340603), CoordinateSystem.WGS84, chinaCellNode))
|
||||
// println(AdminArea.determineAdmin((119.55377645685893,34.639698133907125), CoordinateSystem.WGS84, chinaCellNode))
|
||||
// println(AdminArea.determineAdmin((111.739463,40.842317), CoordinateSystem.GCJ02, chinaCellNode))
|
||||
// println(AdminArea.determineAdmin((111.731608,40.847438), CoordinateSystem.GCJ02, chinaCellNode))
|
||||
|
||||
testSource.zip(testResult).foreach( {
|
||||
case (lnglat, adminR) =>
|
||||
val admin = GeoTrans.determineAdmin(lnglat._1, lnglat._2, CoordinateSystem.GCJ02)
|
||||
println(admin)
|
||||
println(adminR)
|
||||
|
||||
assert(admin.province == adminR.province)
|
||||
assert(admin.city == adminR.city)
|
||||
assert(admin.cityCode == adminR.cityCode)
|
||||
assert(admin.district == adminR.district)
|
||||
assert(admin.districtCode == adminR.districtCode)
|
||||
})
|
||||
|
||||
// List().map(s => {
|
||||
// val start = new S2CellId(s).toLatLng
|
||||
// (start.lngDegrees(), start.latDegrees())
|
||||
// }).foreach(println)
|
||||
|
||||
// val start = System.currentTimeMillis()
|
||||
|
||||
// List((114.905215,4.887684), (101.049631,41.944256), (115.132069,37.300413), (99.15034016844125,22.16272593989059)).foreach(s => {
|
||||
// val admin = GeoSpatial.determineAdmin(s._1, s._2, true, coordSys = CoordinateSystem.WGS84)
|
||||
// println(admin, s)
|
||||
// })
|
||||
|
||||
// val sss = Source.fromFile("/home/dengxq/Downloads/loc.csv").getLines()
|
||||
// .map(s => {
|
||||
// val parts = s.split(",")
|
||||
// val admin = GeoSpatial.determineAdminCode(parts(2).toDouble, parts(3).toDouble)
|
||||
// (admin, parts(2), parts(3))
|
||||
// })
|
||||
// .toList
|
||||
// println("cost3 = " + (System.currentTimeMillis() - start))
|
||||
// println(sss.size)
|
||||
// println(ss.maxBy(_._1)) //&& s._2 != "530827"
|
||||
// println(ss.map(_._1).sum / ss.size)
|
||||
// println(ss.map(_._1).sum)
|
||||
// println(sss.count(s => s._1 != -1))
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,261 +0,0 @@
|
||||
package com.dengxq.lnglat2Geo
|
||||
|
||||
import com.dengxq.lnglat2Geo.GeoTransImpl.min_level
|
||||
import com.dengxq.lnglat2Geo.build.AdminDataProvider.DistrictLoader
|
||||
import com.dengxq.lnglat2Geo.entity.{Admin, Location}
|
||||
import com.dengxq.lnglat2Geo.utils.S2Utils
|
||||
import com.dengxq.lnglat2Geo.utils.S2Utils.{childrenCellId, getLevel}
|
||||
import com.google.common.geometry._
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FlatSpec
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class GeoTransTest extends FlatSpec {
|
||||
|
||||
val testSource = Array(
|
||||
// (112.596628,34.973136), //
|
||||
// (122.376448,29.972796), //
|
||||
// (119.203405,39.374154), //
|
||||
(121.1572265625, 23.9260130330), // 中国台湾省南投县仁爱乡
|
||||
(112.567757, 35.096176), // 济源
|
||||
(116.9565868378, 39.6513677208), // 天津市武清区河西务镇
|
||||
(100.4315185547, 21.7594997307), // 中国云南省西双版纳傣族自治州勐海县勐混镇
|
||||
(85.5670166016, 41.5548386631), // 中国新疆维吾尔自治区巴音郭楞蒙古自治州库尔勒市 普惠乡
|
||||
(117.9969406128, 27.7447712551), // 中国福建省南平市武夷山市 崇安街道
|
||||
(110.8520507813, 34.0526594214), // 河南省三门峡市卢氏县 瓦窑沟乡下河村
|
||||
(116.4811706543, 39.9255352817), // 北京市朝阳区 六里屯街道甜水园
|
||||
(116.3362348080, 40.0622912084), // 北京市昌平区 回龙观地区吉晟别墅社区
|
||||
(116.3362830877, 40.0594500522), // 北京市北京市昌平区 建材城西路65号
|
||||
(116.3325601816, 40.0397393499), // 北京市海淀区 清河街道 小米六期
|
||||
(117.0977783203, 36.5085323575), // 山东省济南市历城区
|
||||
(118.6358642578, 35.8356283889), // 山东省临沂市沂水县
|
||||
(119.7853088379, 36.3029520437), // 山东省潍坊市高密市柏城镇
|
||||
(119.8567199707, 36.2808142593), // 山东省青岛市胶州市胶西镇
|
||||
(120.3892135620, 36.2777698228), // 山东省青岛市城阳区流亭街道于家社区
|
||||
(116.3830447197, 39.9467430610), // 中国北京市北京市西城区鼓楼西大街171号
|
||||
(116.3830447197, 39.9467430610), // 中国北京市北京市西城区鼓楼西大街171号
|
||||
(116.3854265213, 39.9444070723) // 北京市西城区什刹海街道鼓西社区
|
||||
// (116.3363742828, 40.0300297342) // 北京市海淀区清河街道 五彩城购物中心东区
|
||||
)
|
||||
|
||||
val testResult = Array(
|
||||
Admin.createProvincialCity("台湾省","南投县", 710008,710008, Location(0.0,0.0)),
|
||||
Admin.createProvincialCity("河南省","济源市", 419001, 419001, Location(112.602256,35.067199)),
|
||||
Admin.createDistrict("天津市", "天津城区", "武清区", 120100, 120100, 120114, Location(117.044387,39.384119)),
|
||||
Admin.createDistrict( "云南省", "西双版纳傣族自治州", "勐海县", 532800,532800, 532822, Location(100.452547, 21.957353)),
|
||||
Admin.createDistrict("新疆维吾尔自治区", "巴音郭楞蒙古自治州", "库尔勒市", 652800, 652800, 652801, Location(86.174633,41.725891)),
|
||||
Admin.createDistrict("福建省", "南平市", "武夷山市", 350700, 350700, 350782, Location(118.035309,27.756647)),
|
||||
Admin.createDistrict("河南省", "三门峡市", "卢氏县", 411200, 411200, 411224, Location(111.047858,34.054324)),
|
||||
Admin.createDistrict("北京市", "北京城区", "朝阳区", 110100, 110100, 110105, Location(116.443205,39.921506)),
|
||||
Admin.createDistrict("北京市", "北京城区", "昌平区", 110100, 110100, 110114, Location(116.231254,40.220804)),
|
||||
Admin.createDistrict("北京市", "北京城区", "昌平区", 110100, 110100, 110114, Location(116.231254,40.220804)),
|
||||
Admin.createDistrict("北京市", "北京城区", "海淀区", 110100, 110100, 110108, Location(116.298262,39.95993)),
|
||||
Admin.createDistrict("山东省", "济南市", "历城区", 370100, 370100, 370112, Location(117.06523,36.680259)),
|
||||
Admin.createDistrict("山东省", "临沂市", "沂水县", 371300, 371300, 371323, Location(118.627917,35.79045)),
|
||||
Admin.createDistrict( "山东省", "潍坊市", "高密市", 370700, 370700, 370785, Location(119.755597,36.382594)),
|
||||
Admin.createDistrict("山东省", "青岛市", "胶州市", 370200, 370200, 370281, Location(120.033382,36.26468)),
|
||||
Admin.createDistrict( "山东省", "青岛市", "城阳区", 370200, 370200, 370214, Location(120.396256,36.307559))
|
||||
)
|
||||
|
||||
"GeoSpatial.aroundAreas in scala" should "work" in {
|
||||
// testSource.foreach {lnglat=>
|
||||
// val area = AdminArea.aroundAreas(lnglat, CoordinateSystem.GCJ02, Seq(3000, 4000, 5000), chinaCellNode, chinaAreaData).areas
|
||||
// if(area.nonEmpty)
|
||||
// println(area)
|
||||
// }
|
||||
|
||||
|
||||
// val a = (116.3157212734,40.0540260711)
|
||||
val a = (116.3747191429, 39.9085182400)
|
||||
|
||||
// println(geoSpatial.determineAdmin(a, CoordinateSystem.GCJ02).toShort)
|
||||
// println(geoSpatial.determineAdmin(a, true, CoordinateSystem.GCJ02).toShort)
|
||||
// println(geoSpatial.determineArea(a, CoordinateSystem.GCJ02))
|
||||
// println(geoSpatial.determineArea(a, CoordinateSystem.GCJ02, needStreet = true))
|
||||
// println(geoSpatial.aroundMultiRadAreas(a, CoordinateSystem.GCJ02, Seq(1000, 2000, 3000, 4000, 5000)))
|
||||
// println(geoSpatial.aroundAreas(a, CoordinateSystem.GCJ02))
|
||||
}
|
||||
|
||||
"GeoSpatial.determineAdmin in oversea" should "success" in {
|
||||
// val a = (90.417247,23.815664)
|
||||
// val admin = geoSpatial.determineAdmin(a, CoordinateSystem.GCJ02)
|
||||
// println(admin)
|
||||
// assert(admin == Admin.createOversea)
|
||||
}
|
||||
|
||||
"AAA in spark" should "work" in {
|
||||
// val sparkConf = new SparkConf()
|
||||
// .setAppName("test")
|
||||
// .setMaster("local[2]")
|
||||
// .setKryo()
|
||||
//
|
||||
// val sparkContext = new SparkContext(sparkConf)
|
||||
//
|
||||
// val gs = sparkContext.broadcast(geoSpatial)
|
||||
//
|
||||
// sparkContext.parallelize(testSource).repartition(3).map(lnglat=>{
|
||||
// gs.value.determineArea(lnglat, CoordinateSystem.GCJ02, needStreet = true).toShortNameString
|
||||
// }).collect().foreach(println)
|
||||
|
||||
// val nameData:Array[AdminNameNode] = NameMappingDataProvider.loadSerializeFile()
|
||||
//
|
||||
// println(nameData.length)
|
||||
// nameData.flatMap(_.children).take(5).foreach(println)
|
||||
//
|
||||
// val obj : Array[AdminNameData] = NameMappingDataProvider.loadTxtFile()
|
||||
//
|
||||
// println("cost = " + (System.currentTimeMillis() - start))
|
||||
//
|
||||
// println(obj.length)
|
||||
// obj.take(5).foreach(println)
|
||||
|
||||
// val p = Point2d(118.875429,25.587189)
|
||||
|
||||
// list为多边形边界,p为一个待测点
|
||||
|
||||
// val chinaData = DistrictLoader.loadChinaData.get
|
||||
|
||||
import collection.JavaConversions._
|
||||
|
||||
// val data = genCellAdmin()
|
||||
//
|
||||
// data.take(10).foreach(println)
|
||||
// println(data.length)
|
||||
|
||||
val chinaPoly = DistrictLoader.loadAMapJson("country/" + 100000 + ".json")
|
||||
.get.polyline.get
|
||||
val chinaCell = S2Utils.loadS2CellUnion(chinaPoly, min_level, min_level, 10000)
|
||||
.cellIds()
|
||||
.flatMap(s2CellId => {
|
||||
val cellLevel = getLevel(s2CellId.id)
|
||||
if (cellLevel == min_level) List(s2CellId.id())
|
||||
else childrenCellId(s2CellId, cellLevel, min_level).map(_.id()).toList
|
||||
})
|
||||
.distinct
|
||||
.map(s => (s, 1))
|
||||
.toMap
|
||||
|
||||
val startS2: S2LatLng = S2LatLng.fromDegrees(0.8293, 72.004) //左下角
|
||||
val endS2: S2LatLng = S2LatLng.fromDegrees(55.8271, 137.8347) //右上角
|
||||
val rect: S2LatLngRect = new S2LatLngRect(startS2, endS2)
|
||||
val coverer: S2RegionCoverer = new S2RegionCoverer
|
||||
coverer.setMaxLevel(7)
|
||||
coverer.setMinLevel(7)
|
||||
val ss = coverer.getCovering(rect).cellIds()
|
||||
.flatMap(s2CellId => {
|
||||
val cellLevel = getLevel(s2CellId.id)
|
||||
if (cellLevel == min_level) List(s2CellId.id())
|
||||
else childrenCellId(s2CellId, cellLevel, min_level).map(_.id()).toList
|
||||
})
|
||||
.distinct
|
||||
.map(s => (s, chinaCell.getOrElse(s, -1)))
|
||||
.filter(_._2 == -1)
|
||||
|
||||
ss.take(10).foreach(println)
|
||||
println(ss.size)
|
||||
// val vertices = new util.ArrayList[S2Point]
|
||||
// //注意,一般需要多边形内侧,此处需要按照逆时针顺序添加。
|
||||
// vertices.add(S2LatLng.fromDegrees(lat, lng).toPoint)
|
||||
// val s2Loop = new S2Loop(vertices)
|
||||
// val polygon = new S2Polygon(s2Loop)
|
||||
|
||||
|
||||
// val boundaryIndex: Map[Long, List[Long]] =
|
||||
// AdminDataProvider.AdminLoader.loadBoundaryData
|
||||
// .keySet
|
||||
// .map(s => (new S2CellId(s).parent(min_level).id(), s))
|
||||
// .groupBy(_._1)
|
||||
// .map(s => (s._1, s._2.map(_._2).toList))
|
||||
//
|
||||
// val cells = DistrictLoader.loadAMapJson("country/" + 100000 + ".json")
|
||||
// .get
|
||||
// .districts
|
||||
// .flatMap(province => {
|
||||
// val provinceData = DistrictLoader.loadAMapJson("province/" + province.adcode + ".json")
|
||||
// if (provinceData.nonEmpty) {
|
||||
// val cityDatas = provinceData.get
|
||||
// .districts
|
||||
// .flatMap(city => {
|
||||
// val cityData = DistrictLoader.loadAMapJson("city/" + city.adcode + ".json")
|
||||
// if (cityData.nonEmpty) {
|
||||
// val districtDatas = cityData.get.districts.map(dis => DistrictLoader.loadAMapJson("district/" + dis.adcode + ".json"))
|
||||
// if (districtDatas.exists(s => s.nonEmpty && s.get.polyline.nonEmpty)) districtDatas
|
||||
// else List(cityData)
|
||||
// } else {
|
||||
// val districtData = DistrictLoader.loadAMapJson("district/" + city.adcode + ".json")
|
||||
// if (districtData.nonEmpty && districtData.get.polyline.nonEmpty) List(districtData)
|
||||
// else List.empty
|
||||
// }
|
||||
// })
|
||||
// if (cityDatas.exists(s => s.nonEmpty && s.get.polyline.nonEmpty)) cityDatas
|
||||
// else List(provinceData)
|
||||
// } else List.empty
|
||||
// })
|
||||
// .flatMap(admin => {
|
||||
// if (admin.nonEmpty && admin.get.polyline.nonEmpty)
|
||||
// AdminLoader.loadS2CellUnion(admin.get.polyline.get, min_level, min_level, 1000)
|
||||
// .cellIds()
|
||||
// .flatMap(s2CellId => {
|
||||
// val cellLevel = getLevel(s2CellId.id)
|
||||
// if (cellLevel == min_level) List(s2CellId.id())
|
||||
// else childrenCellId(s2CellId, cellLevel, min_level).map(_.id()).toList
|
||||
// })
|
||||
// .distinct
|
||||
// .map(s => (s, boundaryIndex.getOrElse(s, List.empty)))
|
||||
// .filter(_._2.isEmpty)
|
||||
// .map(s => (s._1, admin.get.adcode))
|
||||
// else List.empty
|
||||
// })
|
||||
// .groupBy(_._1)
|
||||
// .filter(_._2.length == 1)
|
||||
// .flatMap(s => s._2)
|
||||
//
|
||||
// println(cells.size)
|
||||
|
||||
|
||||
// DistrictLoader.loadAMapJson("country/" + 100000 + ".json")
|
||||
// .get
|
||||
// .polyline
|
||||
// .get
|
||||
// .split('|').map(loopStr => {
|
||||
// loopStr.split(';').map(coordStr => {
|
||||
// val parts = coordStr.split(',')
|
||||
// val lng = parts(0).toDouble
|
||||
// val lat = parts(1).toDouble
|
||||
// vertices.add(S2LatLng.fromDegrees(lat, lng))
|
||||
// })
|
||||
// })
|
||||
|
||||
|
||||
// val gcj02LonLat = GeoUtils.toGCJ02(120.152983,36.119759, CoordinateSystem.WGS84)
|
||||
// val gcj02LonLat2 = GeoUtils.toGCJ02(99.311893,22.005211, CoordinateSystem.WGS84)
|
||||
//
|
||||
// println(gcj02LonLat)
|
||||
//
|
||||
// val start = System.currentTimeMillis()
|
||||
|
||||
// val districtAdmin = geoSpatial.determineAdmin(gcj02LonLat, chinaData)
|
||||
// val districtAdmin2 = geoSpatial.determineAdmin(gcj02LonLat2, chinaData)
|
||||
|
||||
// println("cost = " + (System.currentTimeMillis() - start))
|
||||
|
||||
// println(districtAdmin)
|
||||
// println(districtAdmin2)
|
||||
}
|
||||
|
||||
// "BBB in spark" should "work" in {
|
||||
// val sparkConf = new SparkConf()
|
||||
// .setAppName("test")
|
||||
// .setMaster("local[2]")
|
||||
// .setKryo()
|
||||
//
|
||||
// val sparkContext = new SparkContext(sparkConf)
|
||||
//
|
||||
// val gs = sparkContext.broadcast(new GeoSpatial())
|
||||
//
|
||||
// sparkContext.parallelize(testSource).repartition(3).map(lnglat=>{
|
||||
// gs.value.determineArea(lnglat, CoordinateSystem.GCJ02, needStreet = true)
|
||||
// }).collect().foreach(println)
|
||||
// }
|
||||
}
|
Loading…
Reference in New Issue
Block a user