This commit is contained in:
virjar 2022-05-27 20:53:20 +08:00
parent 6bc38a94be
commit ea2a5dd645
65 changed files with 2784 additions and 3381 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.iml
.idea/
target/
src/main/resources/json/

View File

@ -1,4 +1,41 @@
# lnglat2Geo
# ChinaGeoLib
修改自: https://github.com/deng0515001/lnglat2Geo 非常感谢原作者的贡献,我完全照搬了原作者的算法实现,但是我不能理解这些算法的原理。
修改点如下:
1. 使用java完全重写Scala代码原因如下
1. 作为libjava和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
View File

@ -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>

View 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
// }
}

View 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);
}
}

View 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
);
}
}

View File

@ -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;
}

View 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";
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View 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;
}

View 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();
}
}

View File

@ -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;
}
}

View File

@ -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;// 墨卡托坐标系
}

View File

@ -0,0 +1,21 @@
package com.dengxq.lnglat2Geo.entity.enums;
public enum DistrictLevel {
/**
* 街道*
*/
Country,
/**
* ,自治区*
*/
Province,
/**
* 地级市*
*/
City,
/**
* ,,县级市*
*/
District,
Street
}

View 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;
}
}

View 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;
}
}

View 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;
}
}
}

View 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

View 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;
}
}

View 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);
}
}

View 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;
}
}
}

View 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;// 返回点到线的距离利用三角形面积公式求高
}
}

View 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);
}
}

View 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;
}
}

View 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.

View File

@ -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,四线城市

View File

@ -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.

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
)
}

View File

@ -1,4 +0,0 @@
package com.dengxq.lnglat2Geo.entity
@SerialVersionUID(-4986836039872467881L)
case class AdminBoundary(var code:Int, var boundary : Array[Array[Long]]) extends Serializable

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -1,4 +0,0 @@
package com.dengxq.lnglat2Geo.entity
@SerialVersionUID(6694671327358697642L)
case class CellAdmin(var adCode : Int, var cellId:Long) extends Serializable

View File

@ -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"
}

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
)
}

View File

@ -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]
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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)
//
// }
}

View File

@ -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))
}
}

View File

@ -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)
// }
}