我们需要在项目中引入com.vividsolutions.jts这个包,**JTS**是加拿大的 Vivid Solutions公司做的一套开放源码的 Java API。它提供了一套空间数据操作的核心算法。为在兼容OGC标准的空间对象模型中进行基础的几何操作提供2D空间谓词API。
全球的行政区划分文件可以在 gadm官网https://gadm.org/download_world.html上下载,
maven仓库为
```xml
<!-- https://mvnrepository.com/artifact/com.vividsolutions/jts -->
<dependency>
<groupId>com.vividsolutions</groupId>
<artifactId>jts</artifactId>
<version>1.13</version>
</dependency>
<dependency>
<groupId>com.vividsolutions</groupId>
<artifactId>jts-io</artifactId>
<version>1.14.0</version>
</dependency>
```
```java
@Component
public class GlobalSearch{
private static final Logger logger = LoggerFactory.getLogger(GlobalSearch.class);
// only used for world
private static Map<IndexedPointInAreaLocator, Geometry> worldMap = new HashMap<>();
private static STRtree worldStrTree = new STRtree();
static {
init();
}
public GlobalDistrict queryGlobalDistrict(CoordinatePoint coordinate, String coordinateType) throws Exception {
if(coordinate==null){
logger.error("coordinate should not be null");
return null;
}
if (coordinateType != null ) {
//GCJ-02 to WGS-84
coordinate = CoordinateUtils.gcj_decrypt(coordinate.getLat(), coordinate.getLon());
}
return queryGlobalDistrict(coordinate);
}
private GlobalDistrict queryGlobalDistrict(CoordinatePoint coordinate) throws Exception {
final Coordinate co = new Coordinate(coordinate.getLon(), coordinate.getLat());
final List<GlobalDistrict> region = new ArrayList<>();
worldStrTree.query(new Envelope(co), new ItemVisitor() {
@Override
public void visitItem(Object item) {
IndexedPointInAreaLocator g = (IndexedPointInAreaLocator) item;
int location = g.locate(co);
if (location == 0 || location == 1) {
region.add((GlobalDistrict) worldMap.get(g).getUserData());
}
}
});
if (region.isEmpty()) {
return null;
}
if (region.size() > 1) {
logger.error("More than one region is found. Coordinate=" + coordinate + ", region=" + region);
}
return region.get(0);
}
private static void init() {
logger.info("JVM max: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + " MB");
try {
// init world
initWorld();
} catch (Exception e) {
logger.error("init error", e);
System.exit(1);
}
}
private static void initWorld() throws Exception {
GeometryFactory f = new GeometryFactory();
int count = 0;
String file = "/home/admin/gadm36.geojson";
//文件比较大,通过JSONReader加载
JSONReader r;
BufferedReader in = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(new File(file))), "utf-8"), 5 * 1024 * 1024);
r = new JSONReader(in);
// r=new JSONReader(new FileReader(file));
r.startObject();
//解析gadm的文件格式
while (r.hasNext()) {
String key = r.readString();
switch (key) {
case "features":
r.startArray();
while (r.hasNext()) {
GlobalDistrict gDistrict = new GlobalDistrict();
Geometry polygon = null;
r.startObject();
while (r.hasNext()) {
String itemKey = r.readString();
switch (itemKey) {
case "properties":
JSONObject properties = (JSONObject) r.readObject();
int objectId = properties.getInteger("UID");
String iso = properties.getString("GID_0");
String code = "-1";
gDistrict.setId(objectId);
gDistrict.setIso(iso);
gDistrict.setCode(code);
String[] upLevelNames = null;
for (int nameIndex = 5; nameIndex >= 0; nameIndex --) {
if (properties.getString("NAME_" + nameIndex) != null) {
gDistrict.setLevel(nameIndex);
gDistrict.setName(properties.getString("NAME_" + nameIndex));
upLevelNames = new String[nameIndex];
break;
}
if (nameIndex == 0) {
Utils.throwException(ExceptionCode.INTERNAL_ERROR, "All of NAME_x fields are null");
}
}
for (int level = 0; level < upLevelNames.length; level ++) {
upLevelNames[level] = properties.getString("NAME_" + level);
}
gDistrict.setUpLevelNames(upLevelNames);
break;
case "geometry":
r.startObject();
String type = "";
while (r.hasNext()) {
String geometryKey = r.readString();
switch (geometryKey) {
case "type":
type = r.readString();
break;
case "coordinates":
// first list, only 1 element
r.startArray();
if (type.equals("Polygon")) {
boolean hasHoles = false;
List<LinearRing> holes = new ArrayList<>();
LinearRing outerRing = null;
while (r.hasNext()) {
List<Coordinate> coordinates = new ArrayList<Coordinate>();
r.startArray();
while (r.hasNext()) {
// coordinate
Coordinate c = new Coordinate();
int i = 0;
r.startArray();
while (r.hasNext()) {
c.setOrdinate(i, Double.parseDouble(r.readString()));
i++;
}
r.endArray();
coordinates.add(c);
}
r.endArray();
if (!hasHoles) {
outerRing = f.createLinearRing(coordinates.toArray(new Coordinate[] {}));
} else {
holes.add(f.createLinearRing(coordinates.toArray(new Coordinate[] {})));
}
hasHoles = true;
}
if (holes.size() == 0) {
polygon = f.createPolygon(outerRing);
} else {
polygon = f.createPolygon(outerRing, holes.toArray(new LinearRing[] {}));
}
} else if (type.equals("MultiPolygon")) {
List<Polygon> polygons = new ArrayList<>();
while (r.hasNext()) {
r.startArray();
boolean hasHoles = false;
List<LinearRing> holes = new ArrayList<>();
LinearRing outerRing = null;
while (r.hasNext()) {
List<Coordinate> coordinates = new ArrayList<Coordinate>();
r.startArray();
while (r.hasNext()) {
// coordinate
Coordinate c = new Coordinate();
int i = 0;
r.startArray();
while (r.hasNext()) {
c.setOrdinate(i, Double.parseDouble(r.readString()));
i++;
}
r.endArray();
coordinates.add(c);
}
r.endArray();
if (!hasHoles) {
outerRing = f.createLinearRing(coordinates.toArray(new Coordinate[] {}));
} else {
holes.add(f.createLinearRing(coordinates.toArray(new Coordinate[] {})));
}
hasHoles = true;
}
if (holes.size() == 0) {
polygons.add(f.createPolygon(outerRing));
} else {
polygons.add(f.createPolygon(outerRing, holes.toArray(new LinearRing[] {})));
}
r.endArray();
}
polygon = f.createMultiPolygon(polygons.toArray(new Polygon[] {}));
} else {
Utils.throwException(ExceptionCode.INTERNAL_ERROR, "unknown polygon type: " + type);
}
r.endArray();
break;
default:
System.out.println(r.readObject().toString());
break;
}
}
r.endObject();
break;
default:
r.readObject();
break;
}
}
r.endObject();
polygon.setUserData(gDistrict);
IndexedPointInAreaLocator locator = new IndexedPointInAreaLocator(polygon);
worldMap.put(locator, polygon);
worldStrTree.insert(polygon.getEnvelopeInternal(), locator);
count ++;
if (count % 10000 == 0) {
logger.info(count + " --- " + Runtime.getRuntime().totalMemory() / 1024 / 1024 + " MB");
}
}
r.endArray();
break;
default:
r.readObject();
break;
}
}
r.endObject();
r=null;
logger.info("World polygon size: " + worldMap.size());
// query a point to the strTree for trigger it
final Coordinate co = new Coordinate(12.4, 51.2);
worldStrTree.query(new Envelope(co), new ItemVisitor() {
@Override
public void visitItem(Object item) {
IndexedPointInAreaLocator g = (IndexedPointInAreaLocator) item;
g.locate(co);
}
});
logger.info("World strTree is triggered");
}
}
```
```java
/**
* 坐标系转换
*/
public class CoordinateUtils {
private static final double PI = 3.14159265358979324d;
private static final double x_pi = 3.14159265358979324 * 3000.0 / 180.0;
private static CoordinatePoint delta(double lat, double lon) {
// Krasovsky 1940
//
// a = 6378245.0, 1/f = 298.3
// b = a * (1 - f)
// ee = (a^2 - b^2) / a^2;
double a = 6378245.0; //
double ee = 0.00669342162296594323; //
double dLat = transformLat(lon - 105.0, lat - 35.0);
double dLon = transformLon(lon - 105.0, lat - 35.0);
double radLat = lat / 180.0 * 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) * PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * PI);
CoordinatePoint point = new CoordinatePoint();
point.setLat(dLat);
point.setLon(dLon);
return point;
}
//WGS-84 to GCJ-02
public static CoordinatePoint gcj_encrypt(double wgsLat, double wgsLon) {
CoordinatePoint point = new CoordinatePoint();
if (outOfChina(wgsLat, wgsLon)) {
point.setLat(wgsLat);
point.setLon(wgsLon);
return point;
}
CoordinatePoint d = delta(wgsLat, wgsLon);
point.setLat(wgsLat + d.getLat());
point.setLon(wgsLon + d.getLon());
return point;
}
//GCJ-02 to WGS-84
public static CoordinatePoint gcj_decrypt(double gcjLat, double gcjLon) {
CoordinatePoint point = new CoordinatePoint();
if (outOfChina(gcjLat, gcjLon)) {
point.setLat(gcjLat);
point.setLon(gcjLon);
return point;
}
CoordinatePoint d = delta(gcjLat, gcjLon);
point.setLat(gcjLat - d.getLat());
point.setLon(gcjLon - d.getLon());
return point;
}
//GCJ-02 to WGS-84 exactly
public static CoordinatePoint gcj_decrypt_exact(double gcjLat, double gcjLon) {
double initDelta = 0.01;
double threshold = 0.000000001;
double dLat = initDelta, dLon = initDelta;
double mLat = gcjLat - dLat, mLon = gcjLon - dLon;
double pLat = gcjLat + dLat, pLon = gcjLon + dLon;
double wgsLat, wgsLon, i = 0;
while (true) {
wgsLat = (mLat + pLat) / 2;
wgsLon = (mLon + pLon) / 2;
CoordinatePoint tmp = gcj_encrypt(wgsLat, wgsLon);
dLat = tmp.getLat() - gcjLat;
dLon = tmp.getLon() - gcjLon;
if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) < threshold))
break;
if (dLat > 0)
pLat = wgsLat;
else
mLat = wgsLat;
if (dLon > 0)
pLon = wgsLon;
else
mLon = wgsLon;
if (++i > 10000)
break;
}
//console.log(i);
CoordinatePoint point = new CoordinatePoint();
point.setLat(wgsLat);
point.setLon(wgsLon);
return point;
}
//GCJ-02 to BD-09
public CoordinatePoint bd_encrypt(double gcjLat, double gcjLon) {
double x = gcjLon, y = gcjLat;
double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * this.x_pi);
double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * this.x_pi);
double bdLon = z * Math.cos(theta) + 0.0065;
double bdLat = z * Math.sin(theta) + 0.006;
CoordinatePoint point = new CoordinatePoint();
point.setLat(bdLat);
point.setLon(bdLon);
return point;
}
//BD-09 to GCJ-02
public static CoordinatePoint bd_decrypt(double bdLat, double bdLon) {
double x = bdLon - 0.0065, y = bdLat - 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 gcjLon = z * Math.cos(theta);
double gcjLat = z * Math.sin(theta);
CoordinatePoint point = new CoordinatePoint();
point.setLat(gcjLat);
point.setLon(gcjLon);
return point;
}
private static boolean outOfChina(double lat, double lon) {
if (lon < 72.004 || lon > 137.8347)
return true;
if (lat < 0.8293 || lat > 55.8271)
return true;
return false;
}
private static double transformLat(double x, double y) {
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
return ret;
}
private static double transformLon(double x, double y) {
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
return ret;
}
public static Coordinate toJts(CoordinatePoint p) {
Coordinate c = new Coordinate();
c.setOrdinate(0, p.getLon());
c.setOrdinate(1, p.getLat());
return c;
}
}
```
```java
/**
* 坐标点
*/
public static class CoordinatePoint {
private double lon;
private double lat;
public CoordinatePoint() {
}
public CoordinatePoint(double lon, double lat) {
this.lon = lon;
this.lat = lat;
}
public double getLon() {
return this.lon;
}
public void setLon(double lon) {
this.lon = lon;
}
public double getLat() {
return this.lat;
}
public void setLat(double lat) {
this.lat = lat;
}
@Override
public String toString() {
return "(" + this.lon + ", " + this.lat + ")";
}
public static <T> T fromJson(String json, Class<T> clazz) {
return JSON.parseObject(json, clazz);
}
public String toJson() {
return JSON.toJSONString(this);
}
}
```