0?",":"")+i;
956 | }
957 | if(item==Current)curIdx=i;
958 | }
959 |
960 |
961 | System.out.println("【功能菜单】 当前静态实例Instances["+curIdx+"]");
962 | System.out.println("1. "+(isInit?"重新":"")+"初始化:调用 Init_StoreInWkbsFile -内存占用很低(性能受IO限制)"+(Current.IsStoreInWkbsFile()?" [已初始化]":""));
963 | System.out.println("2. "+(isInit?"重新":"")+"初始化:调用 Init_StoreInMemory -内存占用和json文件差不多大(性能豪放)"+(Current.IsStoreInMemory()?" [已初始化]":""));
964 | if(isInit) {
965 | System.out.println(HR);
966 | System.out.println("3. 测试:基础功能测试");
967 | System.out.println("4. 测试:1万个伪随机点测试");
968 | System.out.println("5. 测试:多线程性能测试");
969 | System.out.println(HR);
970 | System.out.println("6. 查询: QueryPoint 查找坐标点所在省市区乡镇");
971 | System.out.println("A. 查询: QueryGeometry 查找和图形相交的省市区乡镇");
972 | System.out.println("7. 查询: ReadWKT 读取省市区乡镇边界的WKT文本数据");
973 | System.out.println("8. 查询: Debug 读取边界网格划分图形WKT文本数据");
974 | System.out.println(HR);
975 | System.out.println("9. HTTP: 启动本地轻量HTTP API服务");
976 | }
977 | System.out.println(HR);
978 | System.out.println("*. 输入 use 0-"+(AreaCityQuery.Instances.length-1)+" 切换静态实例,list 列出实例信息,当前"+(initIdxs.length()==0?"无已初始化实例":"["+initIdxs+"]已初始化")+"");
979 | System.out.println("*. 输入 exit 退出");
980 | System.out.println();
981 | System.out.println("请输入菜单序号:");
982 | System.out.print("> ");
983 |
984 | boolean waitAnyKey=true;
985 | String inTxt="";
986 | while(true) {
987 | int byt=System.in.read();
988 | inTxt+=(char)byt;
989 |
990 | if(byt!='\n') {
991 | continue;
992 | }
993 | inTxt=inTxt.trim().toUpperCase();
994 | try {
995 | if(inTxt.equals("1") || inTxt.equals("2")) {
996 | Current.ResetInitStatus();
997 | System.gc();
998 |
999 | Init_FromMenu(inTxt.equals("1"));
1000 | if(Current.GetInitStatus()==2) {
1001 | waitAnyKey=false;
1002 | }
1003 | } else if(isInit && inTxt.equals("3")) {
1004 | for(int i : idxs) {
1005 | if(idxs.size()>1) System.out.println("【测试实例"+i+"】 Instances["+i+"]");
1006 | BaseTest(i);
1007 | }
1008 | } else if(isInit && inTxt.equals("4")) {
1009 | for(int i : idxs) {
1010 | if(idxs.size()>1) System.out.println("【测试实例"+i+"】 Instances["+i+"]");
1011 | LargeRndPointTest(i);
1012 | }
1013 | } else if(isInit && inTxt.equals("5")) {
1014 | ThreadRun(idxs);
1015 | waitAnyKey=false;
1016 | } else if(isInit && inTxt.equals("6")) {
1017 | Query_Point();
1018 | waitAnyKey=false;
1019 | } else if(isInit && inTxt.equals("A")) {
1020 | Query_Geometry();
1021 | waitAnyKey=false;
1022 | } else if(isInit && inTxt.equals("7")) {
1023 | Read_WKT();
1024 | waitAnyKey=false;
1025 | } else if(isInit && inTxt.equals("8")) {
1026 | Query_DebugReadWKT();
1027 | waitAnyKey=false;
1028 | } else if(isInit && inTxt.equals("9")) {
1029 | if(StartHttpApiServer(HttpApiServerPort)) {
1030 | waitAnyKey=false;
1031 | }
1032 | } else if(inTxt.startsWith("USE ")) {
1033 | if(InTxt_SetCurrent(inTxt, false)) {
1034 | waitAnyKey=false;
1035 | }else {
1036 | inTxt="";
1037 | System.out.print("> ");
1038 | continue;
1039 | }
1040 | } else if(inTxt.equals("LIST")) {
1041 | for(int i : idxs) {
1042 | AreaCityQuery item=AreaCityQuery.Instances[i];
1043 | QueryInitInfo info=item.GetInitInfo();
1044 | System.out.println((item==Current?"[当前]":"")
1045 | +"实例"+i+": Instances["+i+"] "+(item.IsStoreInMemory()?"Init_StoreInMemory":"Init_StoreInWkbsFile"));
1046 | System.out.println(" Geometry "+info.GeometryCount+" 个(Grid切分Polygon "+info.PolygonCount+" 个)");
1047 | System.out.println(" Data文件: "+info.FilePath_Data);
1048 | System.out.println(" Wkbs文件: "+info.FilePath_SaveWkbs);
1049 | }
1050 | if(idxs.size()==0) {
1051 | System.out.println("没有已初始化的实例信息!");
1052 | }
1053 | } else if(inTxt.equals("EXIT")) {
1054 | System.out.println("bye!");
1055 | return;
1056 | } else {
1057 | inTxt="";
1058 | System.out.println("序号无效,请重新输入菜单序号!");
1059 | System.out.print("> ");
1060 | continue;
1061 | }
1062 | } catch(Exception e) {
1063 | e.printStackTrace();
1064 | }
1065 | break;
1066 | }
1067 |
1068 | if(waitAnyKey) {
1069 | System.out.println("按任意键继续...");
1070 | int n=System.in.read();
1071 | if(n=='\r') {
1072 | System.in.read();
1073 | }
1074 | }
1075 | }
1076 | }
1077 | }
1078 |
--------------------------------------------------------------------------------
/AreaCityQuery.java:
--------------------------------------------------------------------------------
1 | package com.github.xiangyuecn.areacity.query;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.FileOutputStream;
8 | import java.io.InputStreamReader;
9 | import java.io.PrintWriter;
10 | import java.io.StringWriter;
11 | import java.lang.management.ManagementFactory;
12 | import java.lang.management.OperatingSystemMXBean;
13 | import java.lang.reflect.Method;
14 | import java.math.BigDecimal;
15 | import java.math.RoundingMode;
16 | import java.text.DecimalFormat;
17 | import java.text.SimpleDateFormat;
18 | import java.util.ArrayList;
19 | import java.util.Date;
20 | import java.util.HashMap;
21 | import java.util.HashSet;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.regex.Matcher;
25 | import java.util.regex.Pattern;
26 |
27 | import org.locationtech.jts.geom.Coordinate;
28 | import org.locationtech.jts.geom.Envelope;
29 | import org.locationtech.jts.geom.Geometry;
30 | import org.locationtech.jts.geom.GeometryFactory;
31 | import org.locationtech.jts.geom.LinearRing;
32 | import org.locationtech.jts.geom.MultiPolygon;
33 | import org.locationtech.jts.geom.Point;
34 | import org.locationtech.jts.geom.Polygon;
35 | import org.locationtech.jts.geom.PrecisionModel;
36 | import org.locationtech.jts.index.strtree.STRtree;
37 | import org.locationtech.jts.io.WKBReader;
38 | import org.locationtech.jts.io.WKBWriter;
39 | import org.locationtech.jts.io.WKTWriter;
40 | import org.locationtech.jts.operation.distance.DistanceOp;
41 |
42 | /**
43 | * 使用jts库从省市区县乡镇边界数据(AreaCity-JsSpider-StatsGov开源库)或geojson文件中查找出和任意点、线、面有相交的边界,内存占用低,性能优良。
44 | *
45 | * 可用于:
46 | * - 调用 QueryPoint(lng, lat) 查询一个坐标点对应的省市区名称等信息;
47 | * - 调用 ReadWKT_FromWkbsFile(where) 查询获取需要的省市区边界WKT文本数据。
48 | *
49 | * 部分原理:
50 | * 1. 初始化时,会将边界图形按网格动态的切分成小的图形,大幅减少查询时的几何计算量从而性能优异;
51 | * 2. 内存中只会保存小的图形的外接矩形(Envelope),小的图形本身会序列化成WKB数据(根据Init方式存入文件或内存),因此内存占用很低;
52 | * 3. 内存中的外接矩形(Envelope)数据会使用jts的STRTree索引,几何计算查询时,先从EnvelopeSTRTree中初步筛选出符合条件的边界,RTree性能极佳,大幅过滤掉不相关的边界;
53 | * 4. 对EnvelopeSTRTree初步筛选出来的边界,读取出WKB数据反序列化成小的图形,然后进行精确的几何计算(因为是小图,所以读取和计算性能极高)。
54 | *
55 | * jts库地址:https://github.com/locationtech/jts
56 | *
57 | *
58 | *
GitHub: https://github.com/xiangyuecn/AreaCity-Query-Geometry (github可换成gitee)
59 | *
省市区县乡镇区划边界数据: https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov (github可换成gitee)
60 | */
61 | public class AreaCityQuery {
62 | /** 默认提供的0-9的10个静态实例,每个实例可以分别使用一个数据文件进行初始化和查询,当然自己调用new AreaCityQuery()创建一个新实例使用也是一样的 */
63 | static public final AreaCityQuery[] Instances=new AreaCityQuery[] {
64 | new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery()
65 | ,new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery(),new AreaCityQuery()
66 | };
67 |
68 | /**
69 | * 几何计算查询出包含此坐标点的所有边界图形的属性数据(和此坐标点相交):
70 | *
71 | * - 如果坐标点位于图形内部或边上,这个图形将匹配;
72 | * - 如果坐标点位于两个图形的边上,这两个图形将都会匹配;
73 | * - 如果图形存在孔洞,并且坐标点位于孔洞内(不含边界),这个图形将不匹配。
74 | *
75 | *
76 | *
输入坐标参数的坐标系必须和初始化时使用的geojson数据的坐标系一致,否则坐标可能会有比较大的偏移,导致查询结果不正确。
77 | *
如果还未完成初始化,或者查询出错,都会抛异常。
78 | *
本方法线程安全。
79 | *
80 | *
注意:如果此坐标位于界线外侧(如海岸线、境界线)时将不会有边界图形能够匹配包含(就算距离只相差1cm),此时如果你希望能匹配到附近不远的边界图形,请使用QueryPointWithTolerance方法
81 | *
82 | * @param lng 进度坐标值
83 | * @param lat 纬度坐标值
84 | * @param where 可以为null,可选提供一个函数,筛选属性数据(此数据已经过初步筛选),会传入属性的json字符串,如果需要去精确计算这个边界图形是否匹配就返回true,否则返回false跳过这条边界图形的精确计算
85 | * @param res 可以为null,如果提供结果对象,可通过此对象的Set_XXX属性控制某些查询行为,比如设置Set_ReturnWKTKey可以额外返回边界的WKT文本数据;并且本次查询的结果和统计数据将累加到这个结果内(性能测试用)。注意:此结果对象非线程安全
86 | */
87 | public QueryResult QueryPoint(double lng, double lat, Func where, QueryResult res) throws Exception{
88 | CheckInitIsOK();
89 | return QueryGeometry(Factory.createPoint(new Coordinate(lng, lat)), where, res);
90 | }
91 | /**
92 | * 先几何计算查询出包含此坐标点的所有边界图形的属性数据,此时和QueryPoint方法功能完全一致。
93 | *
当没有边界图形包含此坐标点时,会查询出和此坐标点距离最近的边界图形的属性数据,同一级别的边界图形只会返回距离最近的一条属性数据,比如:范围内匹配到多个市,只返回最近的一个市;级别的划分依据为属性中的deep值,deep值为空的为同的一级
94 | * ;结果属性中会额外添加PointDistance(图形与坐标的距离,单位米)、PointDistanceID(图形唯一标识符)两个值;由于多进行了一次范围查询,性能会比QueryPoint方法低些。
95 | *
本方法主要用途是解决:当坐标位于界线外侧(如海岸线、境界线)时QueryPoint方法将不会有边界图形能够匹配包含此坐标(就算距离只相差1cm),本方法将能够匹配到附近不远的边界图形数据。
96 | *
97 | *
更多参数文档请参考QueryPoint方法,本方法线程安全。
98 | *
99 | * @see #QueryPoint(double, double, Func, QueryResult)
100 | * @param toleranceMetre 距离范围容差值,单位米,比如取值2500,相当于一个以此坐标为中心点、半径为2.5km的圆形范围;当没有任何边界图形包含此坐标点时,会查询出与此坐标点的距离不超过此值 且 距离最近的边界图形属性数据;取值为0时不进行范围查找;取值为-1时不限制距离大小,会遍历所有数据导致性能极低
101 | */
102 | public QueryResult QueryPointWithTolerance(double lng, double lat, Func where, QueryResult res, int toleranceMetre) throws Exception {
103 | CheckInitIsOK();
104 | if(res!=null && res.Result==null) throw new Exception("不支持无Result调用");
105 |
106 | int resLen0=res==null?0:res.Result.size();
107 | Point point=Factory.createPoint(new Coordinate(lng, lat));
108 | QueryResult res1=QueryGeometry(point, where, res);
109 | if(res1.Result.size()>resLen0 || toleranceMetre==0) {
110 | return res1; //查找到了的就直接返回
111 | }
112 |
113 | Geometry geom;
114 | if(toleranceMetre>0) { //以点为中心,容差为半径,构造出一个圆,扩大到容差范围进行查找
115 | geom=CreateSimpleCircle(lng, lat, toleranceMetre, 24);
116 | } else { //不限制范围
117 | geom=CreateRect(-180, -90, 180, 90);
118 | }
119 | HashMap propDists=new HashMap<>();
120 | HashMap deepDists=new HashMap<>();
121 | DecimalFormat df=new DecimalFormat("0.00");
122 | res1.QueryCount--;
123 | res1=QueryGeometryProcess(geom, where, res1, new Func