package com.github.xiangyuecn.areacity.query;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.index.strtree.STRtree;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.WKBWriter;
import org.locationtech.jts.io.WKTWriter;
import org.locationtech.jts.operation.distance.DistanceOp;
/**
* 使用jts库从省市区县乡镇边界数据(AreaCity-JsSpider-StatsGov开源库)或geojson文件中查找出和任意点、线、面有相交的边界,内存占用低,性能优良。
*
* 可用于:
* - 调用 QueryPoint(lng, lat) 查询一个坐标点对应的省市区名称等信息;
* - 调用 ReadWKT_FromWkbsFile(where) 查询获取需要的省市区边界WKT文本数据。
*
* 部分原理:
* 1. 初始化时,会将边界图形按网格动态的切分成小的图形,大幅减少查询时的几何计算量从而性能优异;
* 2. 内存中只会保存小的图形的外接矩形(Envelope),小的图形本身会序列化成WKB数据(根据Init方式存入文件或内存),因此内存占用很低;
* 3. 内存中的外接矩形(Envelope)数据会使用jts的STRTree索引,几何计算查询时,先从EnvelopeSTRTree中初步筛选出符合条件的边界,RTree性能极佳,大幅过滤掉不相关的边界;
* 4. 对EnvelopeSTRTree初步筛选出来的边界,读取出WKB数据反序列化成小的图形,然后进行精确的几何计算(因为是小图,所以读取和计算性能极高)。
*
* jts库地址:https://github.com/locationtech/jts
*
*
*
GitHub: https://github.com/xiangyuecn/AreaCity-Query-Geometry (github可换成gitee)
*
省市区县乡镇区划边界数据: https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov (github可换成gitee)
*/
public class AreaCityQuery {
/** 默认提供的0-9的10个静态实例,每个实例可以分别使用一个数据文件进行初始化和查询,当然自己调用new AreaCityQuery()创建一个新实例使用也是一样的 */
static public final AreaCityQuery[] Instances=new AreaCityQuery[] {
new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery()
,new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery()
};
/**
* 几何计算查询出包含此坐标点的所有边界图形的属性数据(和此坐标点相交):
*
* - 如果坐标点位于图形内部或边上,这个图形将匹配;
* - 如果坐标点位于两个图形的边上,这两个图形将都会匹配;
* - 如果图形存在孔洞,并且坐标点位于孔洞内(不含边界),这个图形将不匹配。
*
*
*
输入坐标参数的坐标系必须和初始化时使用的geojson数据的坐标系一致,否则坐标可能会有比较大的偏移,导致查询结果不正确。
*
如果还未完成初始化,或者查询出错,都会抛异常。
*
本方法线程安全。
*
*
注意:如果此坐标位于界线外侧(如海岸线、境界线)时将不会有边界图形能够匹配包含(就算距离只相差1cm),此时如果你希望能匹配到附近不远的边界图形,请使用QueryPointWithTolerance方法
*
* @param lng 进度坐标值
* @param lat 纬度坐标值
* @param where 可以为null,可选提供一个函数,筛选属性数据(此数据已经过初步筛选),会传入属性的json字符串,如果需要去精确计算这个边界图形是否匹配就返回true,否则返回false跳过这条边界图形的精确计算
* @param res 可以为null,如果提供结果对象,可通过此对象的Set_XXX属性控制某些查询行为,比如设置Set_ReturnWKTKey可以额外返回边界的WKT文本数据;并且本次查询的结果和统计数据将累加到这个结果内(性能测试用)。注意:此结果对象非线程安全
*/
public QueryResult QueryPoint(double lng, double lat, Func where, QueryResult res) throws Exception{
CheckInitIsOK();
return QueryGeometry(Factory.createPoint(new Coordinate(lng, lat)), where, res);
}
/**
* 先几何计算查询出包含此坐标点的所有边界图形的属性数据,此时和QueryPoint方法功能完全一致。
*
当没有边界图形包含此坐标点时,会查询出和此坐标点距离最近的边界图形的属性数据,同一级别的边界图形只会返回距离最近的一条属性数据,比如:范围内匹配到多个市,只返回最近的一个市;级别的划分依据为属性中的deep值,deep值为空的为同的一级
* ;结果属性中会额外添加PointDistance(图形与坐标的距离,单位米)、PointDistanceID(图形唯一标识符)两个值;由于多进行了一次范围查询,性能会比QueryPoint方法低些。
*
本方法主要用途是解决:当坐标位于界线外侧(如海岸线、境界线)时QueryPoint方法将不会有边界图形能够匹配包含此坐标(就算距离只相差1cm),本方法将能够匹配到附近不远的边界图形数据。
*
*
更多参数文档请参考QueryPoint方法,本方法线程安全。
*
* @see #QueryPoint(double, double, Func, QueryResult)
* @param toleranceMetre 距离范围容差值,单位米,比如取值2500,相当于一个以此坐标为中心点、半径为2.5km的圆形范围;当没有任何边界图形包含此坐标点时,会查询出与此坐标点的距离不超过此值 且 距离最近的边界图形属性数据;取值为0时不进行范围查找;取值为-1时不限制距离大小,会遍历所有数据导致性能极低
*/
public QueryResult QueryPointWithTolerance(double lng, double lat, Func where, QueryResult res, int toleranceMetre) throws Exception {
CheckInitIsOK();
if(res!=null && res.Result==null) throw new Exception("不支持无Result调用");
int resLen0=res==null?0:res.Result.size();
Point point=Factory.createPoint(new Coordinate(lng, lat));
QueryResult res1=QueryGeometry(point, where, res);
if(res1.Result.size()>resLen0 || toleranceMetre==0) {
return res1; //查找到了的就直接返回
}
Geometry geom;
if(toleranceMetre>0) { //以点为中心,容差为半径,构造出一个圆,扩大到容差范围进行查找
geom=CreateSimpleCircle(lng, lat, toleranceMetre, 24);
} else { //不限制范围
geom=CreateRect(-180, -90, 180, 90);
}
HashMap propDists=new HashMap<>();
HashMap deepDists=new HashMap<>();
DecimalFormat df=new DecimalFormat("0.00");
res1.QueryCount--;
res1=QueryGeometryProcess(geom, where, res1, new Func