Test.java 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. package com.github.xiangyuecn.areacity.query;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.File;
  4. import java.io.FileOutputStream;
  5. import java.lang.reflect.Method;
  6. import java.util.ArrayList;
  7. import java.util.Random;
  8. import java.util.regex.Matcher;
  9. import java.util.regex.Pattern;
  10. import org.locationtech.jts.geom.Coordinate;
  11. import org.locationtech.jts.geom.Geometry;
  12. import org.locationtech.jts.io.WKTReader;
  13. import com.github.xiangyuecn.areacity.query.AreaCityQuery.Func;
  14. import com.github.xiangyuecn.areacity.query.AreaCityQuery.QueryInitInfo;
  15. import com.github.xiangyuecn.areacity.query.AreaCityQuery.QueryResult;
  16. /**
  17. * AreaCityQuery测试控制台主程序
  18. *
  19. * GitHub: https://github.com/xiangyuecn/AreaCity-Query-Geometry (github可换成gitee)
  20. * 省市区县乡镇区划边界数据: https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov (github可换成gitee)
  21. */
  22. public class Test {
  23. static public void main(String[] args) throws Exception {
  24. //【请在这里编写你自己的测试代码】
  25. /***【运行时直接启动http api服务】 可解开这个注释即可生效
  26. // 比如在服务器里面用,使用这段代码不会显示命令行菜单,会直接启动http服务;linux服务器中可使用 `nohup bash xxx.sh` 命令执行后台运行
  27. Init_FromFile(0, true, "D:/xxx/ok_geo.json"); //支持使用.json、.geojson、.wkbs文件进行初始化
  28. //Init_FromFile(1, true, "D:/xxx/xx1.json"); //允许使用不同的数据文件初始化多个静态实例,api接口可以调用不同实例进行查询
  29. //Init_FromFile(2, true, "D:/xxx/xx2.json");
  30. StartHttpApiServer(HttpApiServerPort); //直接启动http api服务,可指定端口
  31. if(true)return;
  32. ***/
  33. /***【基础调用示例代码】 可解开这个注释即可生效
  34. String jsonFile="仅供测试-全国省级GeoJSON数据-大幅简化粗略版.json";
  35. //先获取到查询实例,默认提供的0-9的10个静态实例,每个实例可以分别使用一个数据文件进行初始化和查询,当然自己调用new AreaCityQuery()创建一个新实例使用也是一样的
  36. AreaCityQuery Instance=AreaCityQuery.Instances[0];
  37. //static public final AreaCityQuery Instance=new AreaCityQuery(); //也可以自己创建静态实例
  38. //查询前先初始化,每个实例全局只会初始化一次,每次查询前都调用即可(查询会在初始化完成后进行),两种初始化方式根据自己业务情况二选一
  39. //首次初始化会从.json或.geojson文件中读取边界图形数据,速度比较慢,会自动生成.wkbs结尾的结构化文件,下次初始化就很快了
  40. //首次初始化生成了.wkbs文件后,后续初始化可以只使用此wkbs文件,允许不用再提供geojson文件(数据更新时需删除wkbs文件再重新用geojson文件进行初始化),具体请阅读对应初始化方法的注释文档
  41. Instance.Init_StoreInWkbsFile(jsonFile, jsonFile+".wkbs", true);
  42. //Instance.Init_StoreInMemory("geojson文件路径", "geojson文件路径.wkbs", true);
  43. //Instance.OnInitProgress=(initInfo)->{ ... } //初始化过程中的回调,可以绑定一个函数,接收初始化进度信息(编写时需在Init之前进行绑定)
  44. System.out.println(Instance.GetInitInfo().toString()); //打印初始化详细信息,包括性能信息
  45. //注意:以下查询中所有坐标参数的坐标系必须和初始化时使用的geojson数据的坐标系一致,否则坐标可能会有比较大的偏移,导致查询结果不正确
  46. //查询包含一个坐标点的所有边界图形的属性数据,可通过res参数让查询额外返回wkt格式边界数据
  47. //查询结果的判定:请不要假定查询结果的数量(坐标刚好在边界上可能会查询出多个省市区),也不要假定查询结果顺序(结果中省市区顺序是乱序的),请检查判定res1.Result中的结果是否符合查询的城市级别,比如查询省市区三级:结果中必须且仅有3条数据,并且省市区都有(判断deep=0省|1市|2区 来区分数据的级别),其他一律判定为查询无效
  48. QueryResult res1=Instance.QueryPoint(114.044346, 22.691963, null, null);
  49. //当坐标位于界线外侧(如海岸线、境界线)时QueryPoint方法将不会有边界图形能够匹配包含此坐标(就算距离只相差1cm),下面这个方法将能够匹配到附近不远的边界图形数据;2500相当于一个以此坐标为中心点、半径为2.5km的圆形范围,会查询出在这个范围内和此坐标点距离最近的边界
  50. QueryResult res1_2=Instance.QueryPointWithTolerance(121.993491, 29.524288, null, null, 2500);
  51. //查询和一个图形(点、线、面)有交点的所有边界图形的属性数据,可通过res参数让查询额外返回wkt格式边界数据
  52. Geometry geom=new WKTReader(AreaCityQuery.Factory).read("LINESTRING(114.30115 30.57962, 117.254285 31.824198, 118.785633 32.064869)");
  53. //Geometry geom=AreaCityQuery.CreateSimpleCircle(114.044346, 22.691963, 10000, 24); //以指定坐标为中心点,创建一个半径为10km的圆面,圆面由24个坐标点粗略构成
  54. QueryResult res2=Instance.QueryGeometry(geom, null, null);
  55. //读取省市区的边界数据wkt格式,这个例子会筛选出武汉市所有区县
  56. QueryResult res3=Instance.ReadWKT_FromWkbsFile("wkt_polygon", null, (prop)->{return prop.contains("武汉市 ");}, null);
  57. //此方法会遍历所有边界图形的属性列表,因此可以用来遍历所有数据,提取感兴趣的属性内容,比如查询一个区划编号id对应的城市信息(城市名称、中心点)
  58. QueryResult res4=Instance.ReadWKT_FromWkbsFile(null, null, (prop)->{
  59. prop=(","+prop.substring(1, prop.length()-1)+",").replace("\"", "").replace(" ", ""); //不解析json,简单处理
  60. return prop.contains(",id:42,"); //只查询出id=42(湖北省)的属性数据(注意初始化的geojson中必须要有对应的属性名,这里是id)
  61. }, null);
  62. System.out.println(res1+"\n"+res1_2+"\n"+res2+"\n"+res3+"\n"+res4);
  63. if(true)return;
  64. *****/
  65. Start(args);
  66. }
  67. static int CurrentIdx=0;
  68. static AreaCityQuery Current=AreaCityQuery.Instances[CurrentIdx];
  69. /** 初始化时,只读取区县级数据,否则省市区都读 **/
  70. static boolean Deep2Only=false;
  71. /** http api 服务端口号**/
  72. static int HttpApiServerPort=9527;
  73. /**使用数据文件直接对实例进行初始化*/
  74. static void Init_FromFile(int instance, boolean storeInWkbsFile, String dataFile) throws Exception {
  75. CurrentIdx=instance;
  76. Current=AreaCityQuery.Instances[instance];
  77. Init__(storeInWkbsFile, dataFile);
  78. Current.CheckInitIsOK();
  79. }
  80. static long BootMaxMemory;
  81. /**命令行菜单调用初始化*/
  82. static void Init_FromMenu(boolean storeInWkbsFile) throws Exception {
  83. if(BootMaxMemory==0) BootMaxMemory=Runtime.getRuntime().maxMemory();
  84. if(BootMaxMemory > 300*1000*1000) {
  85. System.out.println("可以在启动参数中配置 -Xmx300m 调小内存来测试。");
  86. }
  87. System.out.println("========== "+(storeInWkbsFile?"Init_StoreInWkbsFile":"Init_StoreInMemory")+" ==========");
  88. File file=new File("./");
  89. String[] files=file.list();
  90. ArrayList<String> jsonFiles=new ArrayList<>();
  91. ArrayList<String> wkbsFiles=new ArrayList<>();
  92. for(String name : files) {
  93. String str=name.toLowerCase();
  94. if(str.endsWith("json") || str.endsWith("geojson")) {
  95. jsonFiles.add(name);
  96. }
  97. if(str.endsWith("wkbs")) {
  98. wkbsFiles.add(name);
  99. }
  100. }
  101. String dataFile="";
  102. System.out.println("Init_StoreInWkbsFile:用加载数据到结构化数据文件的模式进行初始化,推荐使用本方法初始化,边界图形数据存入结构化数据文件中,内存占用很低,查询时会反复读取文件对应内容,查询性能消耗主要在IO上,IO性能极高问题不大。");
  103. System.out.println("Init_StoreInMemory:用加载数据到内存的模式进行初始化,边界图形数据存入内存中,内存占用和json数据文件大小差不多大,查询性能极高;另外可通过设置 Instances[0-9].SetInitStoreInMemoryUseObject=true 来进一步提升性能,但内存占用会增大一倍多。");
  104. System.out.println(HR);
  105. System.out.println("首次初始化会从.json或.geojson文件中读取边界图形数据,并生成.wkbs结尾的结构化文件,速度比较慢(文件读写);下次会直接从.wkbs文件进行初始化(文件只读不写),就很快了,可copy此.wkbs文件到别的地方使用(比如服务器、只读环境中)。");
  106. System.out.println(" - 如果.wkbs文件已存在并且有效,将优先从wkbs文件中读取数据,速度很快。");
  107. System.out.println(" - 你可以在当前目录内放入 .json|.geojson|.wkbs 文件(utf-8),可通过菜单进行选择初始化,否则需要输入文件路径。");
  108. System.out.println(" - 当前目录:"+new File("").getAbsolutePath());
  109. System.out.println(" - json文件内必须一条数据占一行,如果不是将不支持解析,请按下面的方法生成一个json文件测试。");
  110. System.out.println(HR);
  111. System.out.println("如何获取省市区县乡镇边界数据json文件:");
  112. System.out.println(" 1. 请到开源库下载省市区边界数据ok_geo.csv文件: https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov (github可换成gitee);");
  113. System.out.println(" 2. 下载开源库里面的“AreaCity-Geo格式转换工具软件”;");
  114. System.out.println(" 3. 打开转换工具软件,选择ok_geo.csv,然后导出成geojson文件即可(默认会导出全国的省级数据,通过填写不同城市名前缀可以导出不同城市);");
  115. System.out.println(" 4. 将导出的json文件复制到本java程序目录内,然后重新运行初始化即可解析使用此文件。");
  116. System.out.println(HR);
  117. boolean useInputFile=false;
  118. boolean useChoice=false;
  119. if(jsonFiles.size()>0 || wkbsFiles.size()>0) {
  120. useChoice=true;
  121. } else {
  122. System.out.println("在当前目录内未发现任何一个 .json|.geojson|.wkbs 文件,需手动填写文件路径。");
  123. System.out.println();
  124. useInputFile=true;
  125. }
  126. if(useChoice) {
  127. System.out.println("在当前目录内发现数据文件,请选择要从哪个文件初始化,请输入文件序号:");
  128. int idx=0;
  129. System.out.println(idx+". 手动输入文件路径");
  130. for(String name : jsonFiles) {
  131. idx++;
  132. System.out.println(idx+". [json]"+name);
  133. }
  134. for(String name : wkbsFiles) {
  135. idx++;
  136. System.out.println(idx+". [wkbs]"+name);
  137. }
  138. while(true) {
  139. System.out.print("> ");
  140. String txt=ReadIn();
  141. if(txt.length()==0){
  142. break;
  143. } else if(txt.equals("0")) {
  144. useInputFile=true;
  145. break;
  146. } else if(txt.length()>0) {
  147. idx=-1;
  148. try { idx=Integer.parseInt(txt); } catch (Exception e){ }
  149. if(idx>0 && idx<=jsonFiles.size()) {
  150. dataFile=jsonFiles.get(idx-1);
  151. } else if(idx>0 && idx-jsonFiles.size()<=wkbsFiles.size()) {
  152. dataFile=wkbsFiles.get(idx-jsonFiles.size()-1);
  153. } else {
  154. System.out.println("输入的序号无效,请重新输入!");
  155. continue;
  156. }
  157. break;
  158. }
  159. }
  160. }
  161. if(useInputFile) {
  162. System.out.println("请输入初始化要读取的一个 .json|.geojson|.wkbs 文件完整路径:");
  163. while(true) {
  164. System.out.print("> ");
  165. String txt=ReadIn();
  166. String str=txt.toLowerCase();
  167. if(txt.length()==0) {
  168. break;
  169. } else if(str.endsWith("json") || str.endsWith("geojson")) {
  170. dataFile=txt;
  171. } else if (str.endsWith("wkbs")) {
  172. dataFile=txt;
  173. } else {
  174. System.out.println("输入的文件类型无效,请重新输入!");
  175. continue;
  176. }
  177. File f=new File(txt);
  178. if(!f.exists()) {
  179. dataFile="";
  180. System.out.println("文件不存在,请重新输入!(未找到文件:"+f.getAbsolutePath()+")");
  181. continue;
  182. }
  183. break;
  184. }
  185. }
  186. Init__(storeInWkbsFile, dataFile);
  187. }
  188. /**省**/static boolean[] HasDeep0=new boolean[AreaCityQuery.Instances.length];
  189. /**市**/static boolean[] HasDeep1=new boolean[AreaCityQuery.Instances.length];
  190. /**区县**/static boolean[] HasDeep2=new boolean[AreaCityQuery.Instances.length];
  191. /**乡镇街道**/static boolean[] HasDeep3=new boolean[AreaCityQuery.Instances.length];
  192. static void Init__(boolean storeInWkbsFile, String dataFile) throws Exception {
  193. String initDataFile,initSaveWkbsFile;
  194. String str=dataFile.toLowerCase();
  195. if(str.endsWith("json") || str.endsWith("geojson")) {
  196. initDataFile=new File(dataFile).getAbsolutePath();
  197. initSaveWkbsFile=initDataFile+".wkbs";
  198. if(!storeInWkbsFile) {
  199. System.out.println("用json文件进行Init_StoreInMemory初始化时,可选提供一个.wkbs后缀的文件路径,初始化时会自动生成此文件,如果不提供将不能查询WKT数据;直接回车提供,输入n不提供:");
  200. System.out.print("> ");
  201. String txt=ReadIn();
  202. if(txt.toLowerCase().equals("n")) {
  203. initSaveWkbsFile="";
  204. }
  205. }
  206. } else if(str.endsWith("wkbs")) {
  207. initDataFile=new File(dataFile).getAbsolutePath();
  208. initSaveWkbsFile="";
  209. } else {
  210. System.out.println("未选择文件,不初始化,已退出!");
  211. return;
  212. }
  213. HasDeep0[CurrentIdx]=HasDeep1[CurrentIdx]=HasDeep2[CurrentIdx]=HasDeep3[CurrentIdx]=false;
  214. //初始化回调
  215. Current.OnInitProgress=new Func<QueryInitInfo, Boolean>() {
  216. long logTime=0;int maxNo=0,lastNo=0;
  217. @Override
  218. public Boolean Exec(QueryInitInfo info) throws Exception {
  219. if(logTime==0) {
  220. if(info.DataFromWkbsFile) {
  221. System.out.println("正在从wkbs结构化数据文件中快速读取数据...");
  222. } else if(info.HasWkbsFile){
  223. System.out.println("首次运行,正在生成wkbs结构化数据文件,速度可能会比较慢...");
  224. } else {
  225. System.out.println("正在从json文件中读取数据,未提供wkbs文件,速度可能会比较慢...");
  226. }
  227. }
  228. if(info.CurrentLine_No!=0) {
  229. maxNo=info.CurrentLine_No;
  230. }
  231. if(info.CurrentLine_No==0 && lastNo!=maxNo || System.currentTimeMillis()-logTime>1000) {
  232. logTime=System.currentTimeMillis();
  233. lastNo=maxNo;
  234. System.out.println("读取第"+lastNo+"行...");
  235. }
  236. if(info.CurrentLine_No!=0) {
  237. String prop=info.CurrentLine_Prop.replace(" ", "").replace("\"", ""); //不解析json,简单处理
  238. if(!Deep2Only && !HasDeep0[CurrentIdx])HasDeep0[CurrentIdx]=prop.contains("deep:0");
  239. if(!Deep2Only && !HasDeep1[CurrentIdx])HasDeep1[CurrentIdx]=prop.contains("deep:1");
  240. if(!HasDeep2[CurrentIdx])HasDeep2[CurrentIdx]=prop.contains("deep:2");
  241. if(!Deep2Only && !HasDeep3[CurrentIdx])HasDeep3[CurrentIdx]=prop.contains("unique_id:");
  242. if(Deep2Only) {
  243. return prop.contains("deep:2"); //只提取区级,其他一律返回false跳过解析
  244. }
  245. }
  246. return true;
  247. }
  248. };
  249. //初始化,如果未生成结构化数据文件(wkbs)这里会从json数据文件自动生成,如果生成了就只会读取wkbs文件
  250. if(storeInWkbsFile) {
  251. Current.Init_StoreInWkbsFile(initDataFile, initSaveWkbsFile, true);
  252. } else {
  253. Current.Init_StoreInMemory(initDataFile, initSaveWkbsFile, true);
  254. }
  255. System.out.println("========== "+(storeInWkbsFile?"Init_StoreInWkbsFile":"Init_StoreInMemory")+" ==========");
  256. System.out.println(Current.GetInitInfo().toString());
  257. System.out.println();
  258. System.out.println("已加载数据级别:"+(HasDeep0[CurrentIdx]?"√":"×")+"省,"+(HasDeep1[CurrentIdx]?"√":"×")+"市,"+(HasDeep2[CurrentIdx]?"√":"×")+"区县,"+(HasDeep3[CurrentIdx]?"√":"×")+"乡镇 (×为未加载,可能是数据文件中并不含此级数据)");
  259. System.out.println();
  260. }
  261. static boolean ResultHas(QueryResult res, String str) {
  262. if(res.Result!=null) {
  263. for(int i=0,L=res.Result.size();i<L;i++) {
  264. if(res.Result.get(i).contains(str)) {
  265. return true;
  266. }
  267. }
  268. }
  269. return false;
  270. }
  271. static void BaseTest(int instanceIdx) throws Exception {
  272. AreaCityQuery instance=AreaCityQuery.Instances[instanceIdx];
  273. int loop=100;
  274. System.out.println("========== QueryPoint ==========");
  275. {
  276. QueryResult res=new QueryResult();
  277. res.Set_EnvelopeHitResult=new ArrayList<>();
  278. //res.Set_ReturnWKTKey="polygon_wkt";
  279. for(int i=0;i<loop;i++) {
  280. res.Result.clear();//清除一下上次的结果,只保留统计
  281. res.Set_EnvelopeHitResult.clear();
  282. res=instance.QueryPoint(114.044346, 22.691963, null, res);
  283. }
  284. System.out.println(res.toString());
  285. if(HasDeep2[instanceIdx]) {
  286. System.out.println(ResultHas(res, "龙华区\"")?"OK":"查询失败!");
  287. }
  288. }
  289. System.out.println();
  290. System.out.println("========== QueryPointWithTolerance ==========");
  291. {
  292. QueryResult res=new QueryResult();
  293. res.Set_EnvelopeHitResult=new ArrayList<>();
  294. double lng=121.993491,lat=29.524288;
  295. QueryResult res2=instance.QueryPoint(lng, lat, null, null);
  296. for(int i=0;i<loop;i++) {
  297. res.Result.clear();//清除一下上次的结果,只保留统计
  298. res.Set_EnvelopeHitResult.clear();
  299. res=instance.QueryPointWithTolerance(lng, lat, null, res, 2500);
  300. }
  301. System.out.println(res.toString());
  302. if(HasDeep2[instanceIdx]) {
  303. System.out.println(ResultHas(res, "象山县\"") && res2.Result.size()==0?"OK":"查询失败!");
  304. }
  305. }
  306. System.out.println();
  307. System.out.println("========== QueryGeometry ==========");
  308. if(!HasDeep0[instanceIdx]){
  309. System.out.println("无省级边界,其他级别返回结果会过多,不测试。");
  310. }else{
  311. double x0=113.305514,y0=30.564249;//河南、安徽、湖北,三省交界的一个超大矩形范围
  312. double x1=117.326510,y1=32.881526;
  313. Geometry geom=AreaCityQuery.Factory.createPolygon(new Coordinate[] {
  314. new Coordinate(x0, y0)
  315. ,new Coordinate(x1, y0)
  316. ,new Coordinate(x1, y1)
  317. ,new Coordinate(x0, y1)
  318. ,new Coordinate(x0, y0)
  319. });
  320. QueryResult res=new QueryResult();
  321. for(int i=0;i<loop;i++) {
  322. res.Result.clear();//清除一下上次的结果,只保留统计
  323. res=instance.QueryGeometry(geom, new Func<String, Boolean>() {
  324. @Override
  325. public Boolean Exec(String prop) throws Exception {
  326. int i0=prop.indexOf("deep\"");//高性能手撸json字符串
  327. if(i0==-1)return false;
  328. int i1=prop.indexOf(",", i0);
  329. if(i1==-1)i1=prop.length();
  330. return prop.substring(i0+6,i1).contains("0");//where条件过滤,只查找省级数据(deep==0,json内就是{deep:0})
  331. }
  332. }, res);
  333. }
  334. System.out.println(res.toString());
  335. System.out.println(ResultHas(res, "湖北省\"")
  336. && ResultHas(res, "河南省\"")
  337. && ResultHas(res, "安徽省\"") ?"OK":"查询失败!");
  338. }
  339. System.out.println();
  340. System.out.println("========== ReadWKT_FromWkbsFile ==========");
  341. {
  342. QueryResult res=new QueryResult();
  343. for(int i=0;i<loop;i++) {
  344. res.Result.clear();//清除一下上次的结果,只保留统计
  345. String wktKey="plygon_wkt";
  346. if(!instance.GetInitInfo().HasWkbsFile) {
  347. if(i==0)System.out.println("【注意】初始化时如果没有提供wkbs文件,不能查询wkt数据");
  348. wktKey="";
  349. }
  350. res=instance.ReadWKT_FromWkbsFile(wktKey, res, new Func<String, Boolean>() {
  351. @Override
  352. public Boolean Exec(String prop) throws Exception {
  353. return prop.contains("北京市 朝阳区\"")
  354. || prop.contains("武汉市 洪山区\"")
  355. || prop.contains("台北市 中山区\"");
  356. }
  357. }, null);
  358. }
  359. System.out.println(res.toString());
  360. if(HasDeep2[instanceIdx]) {
  361. System.out.println(ResultHas(res, "北京市 朝阳区\"")
  362. && ResultHas(res, "武汉市 洪山区\"")
  363. && ResultHas(res, "台北市 中山区\"") ?"OK":"查询失败!");
  364. }
  365. }
  366. System.out.println();
  367. }
  368. static void LargeRndPointTest(int instanceIdx) throws Exception {
  369. AreaCityQuery instance=AreaCityQuery.Instances[instanceIdx];
  370. System.out.println("========== QueryPoint:1万个伪随机点测试 ==========");
  371. System.out.println("伪随机:虽然是随机生成的点,但每次运行生成坐标列表都是相同的。");
  372. for(int loop=0;loop<2;loop++) {
  373. System.out.println((loop==0?"QueryPoint":"QueryPointWithTolerance")+"测试中,请耐心等待...");
  374. QueryResult res=new QueryResult();
  375. double x_0=98.0,y_00=18.0;//矩形范围囊括大半个中国版图
  376. double x_1=135.0,y_1=42.0;
  377. int size=100;//1万点
  378. double xStep=(x_1-x_0)/size;
  379. double yStep=(y_1-y_00)/size;
  380. while(x_0-x_1<-xStep/2) {//注意浮点数±0.000000001的差异
  381. double x0=x_0, x1=x_0+xStep; x_0=x1;
  382. double y_0=y_00;
  383. while(y_0-y_1<-yStep/2) {
  384. double y0=y_0, y1=y_0+yStep; y_0=y1;
  385. if(loop==0) {
  386. res=instance.QueryPoint(x0, y0, null, res);
  387. }else {
  388. res=instance.QueryPointWithTolerance(x0, y0, null, res, 2500);
  389. }
  390. res.Result.clear();//只保留统计
  391. }
  392. }
  393. res.Result=null;
  394. System.out.println(res.toString());
  395. System.out.println();
  396. }
  397. }
  398. static void ThreadRun(ArrayList<Integer> idxs) throws Exception {
  399. System.out.println("========== 多线程性能测试 ==========");
  400. boolean[] stop=new boolean[] {false};
  401. int ThreadCount=Math.max(1, Runtime.getRuntime().availableProcessors()-1);
  402. QueryResult[] SecondCompletes=new QueryResult[ThreadCount];
  403. System.out.println("通过开启 CPU核心数-1 个线程,每个线程内随机查询不同的坐标点,来达到性能测试的目的。");
  404. System.out.println("正在测试中,线程数:"+ThreadCount+",按回车键结束测试...");
  405. //测试函数
  406. Func<Object[], Object> run=new Func<Object[], Object>() {
  407. @Override
  408. public Object Exec(Object[] args) throws Exception {
  409. QueryResult res=(QueryResult)args[0];
  410. int instanceIdx=(int)args[1];
  411. AreaCityQuery instance=AreaCityQuery.Instances[instanceIdx];
  412. //固定坐标点测试
  413. boolean allOk=true;
  414. instance.QueryPoint(114.044346, 22.691963, null, res); //广东省 深圳市 龙华区
  415. allOk&=ResultHas(res, "深圳市 龙华区\"");res.Result.clear();
  416. instance.QueryPoint(117.286491, 30.450399, null, res); //安徽省 铜陵市 郊区 飞地
  417. allOk&=ResultHas(res, "铜陵市 郊区\"");res.Result.clear();
  418. instance.QueryPoint(116.055588, 39.709385, null, res); //北京市 房山区 星城街道 飞地
  419. allOk&=ResultHas(res, "北京市 房山区\"");res.Result.clear();
  420. instance.QueryPoint(130.283168, 47.281807, null, res); // 黑龙江省 鹤岗市 南山区
  421. allOk&=ResultHas(res, "鹤岗市 南山区\"");res.Result.clear();
  422. instance.QueryPoint(118.161624, 39.656532, null, res); // 河北省 唐山市 路北区
  423. allOk&=ResultHas(res, "唐山市 路北区\"");res.Result.clear();
  424. instance.QueryPoint(81.869760, 41.812321, null, res); // 新疆 阿克苏地区 拜城县
  425. allOk&=ResultHas(res, "阿克苏地区 拜城县\"");res.Result.clear();
  426. if(HasDeep2[instanceIdx] && !allOk) {
  427. throw new Exception("查询失败!");
  428. }
  429. //随机坐标点测试
  430. Random rnd=new Random();
  431. int count=100;//只计算100次
  432. double x_0=98.0+rnd.nextDouble(),y_00=21.0+rnd.nextDouble();//矩形范围囊括大半个中国版图
  433. double x_1=122.0+rnd.nextDouble(),y_1=42.0+rnd.nextDouble();
  434. int size=100;//1万点
  435. double xStep=(x_1-x_0)/size;
  436. double yStep=(y_1-y_00)/size;
  437. x_0+=(rnd.nextInt(size*size-count))*xStep; //随机选择开始位置
  438. while(x_0-x_1<-xStep/2) {//注意浮点数±0.000000001的差异
  439. double x0=x_0, x1=x_0+xStep; x_0=x1;
  440. double y_0=y_00;
  441. while(y_0-y_1<-yStep/2) {
  442. double y0=y_0, y1=y_0+yStep; y_0=y1;
  443. instance.QueryPoint(x0, y0, null, res);
  444. res.Result.clear();//只保留统计
  445. count--;
  446. if(count==0) {
  447. return null;
  448. }
  449. }
  450. }
  451. return null;
  452. }
  453. };
  454. //更新统计显示
  455. long startTime=System.currentTimeMillis();
  456. long[] showProgressTime=new long[] {0};
  457. Func<Object,Object> showProgress=new Func<Object, Object>() {
  458. @Override
  459. public Object Exec(Object val) throws Exception {
  460. synchronized (showProgressTime) {
  461. if(System.nanoTime()-showProgressTime[0]<1000*1000000) {
  462. return null;
  463. }
  464. QueryResult res=new QueryResult();
  465. for(int i=0;i<SecondCompletes.length;i++) {
  466. if(SecondCompletes[i]==null) {
  467. return null;
  468. }
  469. SecondCompletes[i].Result=null;
  470. res.Add(SecondCompletes[i]);
  471. }
  472. showProgressTime[0]=System.nanoTime();
  473. res.QueryCount=Math.max(2, res.QueryCount);
  474. res.StartTimeN=0;
  475. res.EndTimeN=1000L*1000000*ThreadCount;
  476. String[] arr=res.toString().split("\n");
  477. long dur=System.currentTimeMillis()-startTime;
  478. long f=dur/60000,m=dur/1000%60;
  479. String s=(f<10?"0":"")+f+":"+(m<10?"0":"")+m;
  480. if(!stop[0]) {
  481. System.out.print("\r"+s
  482. +" QPS["+ThreadCount+"线程 "+res.QueryCount+"]"
  483. +"[单线程 "+res.QueryCount/ThreadCount+"]"
  484. +(idxs.size()>1?"多实例":Current.IsStoreInMemory()?"InMemory":"InWkbsFile")
  485. +" "+arr[1]+"。按回车键结束测试...");
  486. }
  487. return null;
  488. }
  489. }
  490. };
  491. //获得一个线程执行函数
  492. Func<Integer,Func<Object, Object>> newThreadRun=new Func<Integer, Func<Object, Object>>() {
  493. @Override
  494. public Func<Object, Object> Exec(Integer threadId) throws Exception {
  495. return new Func<Object, Object>() {
  496. @Override
  497. public Object Exec(Object val) throws Exception {
  498. QueryResult res=new QueryResult();//这个只能单线程用
  499. Object[] args=new Object[] { res, CurrentIdx };
  500. Random rnd=new Random();
  501. long t1=System.nanoTime();
  502. while(!stop[0]) {
  503. try {
  504. run.Exec(args);
  505. long t0=System.nanoTime();
  506. if(t0-t1>=1000*1000000) {//1秒钟,给赋值一次统计数据
  507. SecondCompletes[threadId]=res;
  508. showProgress.Exec(null);
  509. args[0]=res=new QueryResult();
  510. args[1]=idxs.get(rnd.nextInt(idxs.size()));
  511. t1=System.nanoTime();
  512. }
  513. } catch(Exception e) {
  514. e.printStackTrace();
  515. }
  516. }
  517. return null;
  518. }
  519. };
  520. }
  521. };
  522. //开启多线程
  523. int[] threadCount=new int[] { ThreadCount };
  524. for(int i=0;i<ThreadCount;i++) {
  525. Func<Object, Object> threadRun=newThreadRun.Exec(i);
  526. new Thread(new Runnable() {
  527. public void run() {
  528. try {
  529. threadRun.Exec(null);
  530. } catch(Exception e) {
  531. e.printStackTrace();
  532. } finally {
  533. synchronized (threadCount) { threadCount[0]--; }
  534. }
  535. }
  536. }).start();
  537. }
  538. ReadIn();
  539. stop[0]=true;
  540. System.out.println("等待线程结束...");
  541. while(threadCount[0]>0) {
  542. try { Thread.sleep(10); }catch (Exception e) { }
  543. }
  544. System.out.println("多线程性能测试已结束。");
  545. System.out.println();
  546. }
  547. static void Query_Point() throws Exception {
  548. String idxs=GetInitInstanceIdxs();
  549. System.out.println("========== 查询一个坐标点对应的省市区乡镇数据 ==========");
  550. System.out.println("注意:输入坐标参数的坐标系必须和初始化时使用的geojson数据的坐标系一致,否则坐标可能会有比较大的偏移,导致查询结果不正确。");
  551. System.out.println("请输入一个坐标点,格式:\"lng lat\"(允许有逗号):");
  552. System.out.println(" - 比如:114.044346 22.691963,为广东省 深圳市 龙华区");
  553. System.out.println(" - 比如:117.286491 30.450399,为安徽省 铜陵市 郊区,在池州市 贵池区的飞地");
  554. System.out.println(" - 比如:121.993491 29.524288,为浙江省 宁波市 象山县,但坐标点位于海岸线外侧,不在任何边界内,需设置tolerance才能查出");
  555. System.out.println(" - 输入 tolerance=2500 设置距离范围容差值,单位米,比如2500相当于一个以此坐标为中心点、半径为2.5km的圆形范围;默认0不设置,-1不限制距离;当坐标位于界线外侧(如海岸线、境界线)时QueryPoint方法将不会有边界图形能够匹配包含此坐标(就算距离只相差1cm),设置tolerance后,会查询出在这个范围内和此坐标点距离最近的边界数据");
  556. System.out.println(" - 输入 exit 退出查询,输入 use "+idxs+" 切换查询实例");
  557. int tolerance=0;
  558. while(true){
  559. System.out.print((idxs.length()<2?"":"[实例"+CurrentIdx+"] ")+"> ");
  560. String inStr=ReadIn().trim();
  561. if(inStr.length()==0) {
  562. System.out.println("输入为空,请重新输入!如需退出请输入exit");
  563. continue;
  564. }
  565. if(inStr.equals("exit")) {
  566. System.out.println("bye! 已退出查询。");
  567. System.out.println();
  568. return;
  569. }
  570. if(inStr.startsWith("use ")) {
  571. if(InTxt_SetCurrent(inStr, true)) {
  572. System.out.println("已设置当前实例为"+CurrentIdx);
  573. }
  574. continue;
  575. }
  576. if(inStr.startsWith("tolerance")) {
  577. Matcher m=Pattern.compile("^tolerance[=\\s]+([+-]*\\d+)$").matcher(inStr);
  578. if(!m.find()) {
  579. System.out.println("tolerance设置格式错误,请重新输入");
  580. }else {
  581. tolerance=Integer.parseInt(m.group(1));
  582. System.out.println("已设置tolerance="+tolerance);
  583. }
  584. continue;
  585. }
  586. String[] arr=inStr.split("[,\\s]+");
  587. double lng=-999,lat=-999;
  588. if(arr.length==2) {
  589. try {
  590. lng=Double.parseDouble(arr[0]);
  591. lat=Double.parseDouble(arr[1]);
  592. }catch(Exception e) {
  593. lng=lat=-999;
  594. }
  595. }
  596. if(lng<-180 || lat<-90 || lng>180 || lat>90) {
  597. System.out.println("输入的坐标格式不正确");
  598. continue;
  599. }
  600. QueryResult res;
  601. if(tolerance==0) {
  602. res=Current.QueryPoint(lng, lat, null, null);
  603. }else {
  604. System.out.println("QueryPointWithTolerance tolerance="+tolerance);
  605. res=Current.QueryPointWithTolerance(lng, lat, null, null, tolerance);
  606. }
  607. System.out.println(res.toString());
  608. }
  609. }
  610. static void Query_Geometry() throws Exception {
  611. String idxs=GetInitInstanceIdxs();
  612. System.out.println("========== 查询和任意一个几何图形相交的省市区乡镇数据 ==========");
  613. System.out.println("注意:输入WKT的坐标系必须和初始化时使用的geojson数据的坐标系一致,否则坐标可能会有比较大的偏移,导致查询结果不正确。");
  614. System.out.println("请输入一个WKT文本(Well Known Text):");
  615. System.out.println(" - 比如:POINT(114.044346 22.691963),坐标点,为广东省 深圳市 龙华区");
  616. System.out.println(" - 比如:LINESTRING(114.30115 30.57962, 117.254285 31.824198, 118.785633 32.064869),路径线段,武汉-合肥-南京 三个点连成的线段");
  617. System.out.println(" - 比如:POLYGON((113.305514 30.564249, 113.305514 32.881526, 117.326510 32.881526, 117.326510 30.564249, 113.305514 30.564249)),范围,湖北-河南-安徽 三省交界的一个超大矩形范围");
  618. System.out.println(" - 输入 exit 退出查询,输入 use "+idxs+" 切换查询实例");
  619. while(true){
  620. System.out.print((idxs.length()<2?"":"[实例"+CurrentIdx+"] ")+"> ");
  621. String inStr=ReadIn().trim();
  622. if(inStr.length()==0) {
  623. System.out.println("输入为空,请重新输入!如需退出请输入exit");
  624. continue;
  625. }
  626. if(inStr.equals("exit")) {
  627. System.out.println("bye! 已退出查询。");
  628. System.out.println();
  629. return;
  630. }
  631. if(inStr.startsWith("use ")) {
  632. if(InTxt_SetCurrent(inStr, true)) {
  633. System.out.println("已设置当前实例为"+CurrentIdx);
  634. }
  635. continue;
  636. }
  637. Geometry geom;
  638. try {
  639. geom=new WKTReader(AreaCityQuery.Factory).read(inStr);
  640. }catch(Exception e) {
  641. System.out.println("输入的WKT解析失败:"+e.getMessage());
  642. continue;
  643. }
  644. QueryResult res=Current.QueryGeometry(geom, null, null);
  645. System.out.println(res.toString());
  646. }
  647. }
  648. static void Read_WKT() throws Exception {
  649. System.out.println("========== 读取省市区乡镇边界的WKT文本数据 ==========");
  650. System.out.println("遍历所有边界图形的属性列表查询出符合条件的属性,然后返回图形的属性+边界图形WKT文本。 ");
  651. System.out.println("读取到的wkt文本,可以直接粘贴到页面内渲染显示:https://xiangyuecn.github.io/AreaCity-JsSpider-StatsGov/assets/geo-echarts.html");
  652. System.out.println();
  653. ExtPathExpIn("ReadWKT", new Func<Test.ExtPathExpInArgs, Object>() {
  654. @Override
  655. public Object Exec(ExtPathExpInArgs args) throws Exception {
  656. int[] count=new int[] { 0 };
  657. Current.ReadWKT_FromWkbsFile("", null, new Func<String, Boolean>() {
  658. @Override
  659. public Boolean Exec(String prop) throws Exception {
  660. return ExtPathMatch(prop, args.extPath_exp);
  661. }
  662. }, getWktReadFn("ReadWKT", args, count));
  663. if(count[0] == 0) {
  664. System.out.println("未找到“"+args.extPath_inputTxt+"”匹配的属性!");
  665. } else {
  666. System.out.println("查找完成,共"+count[0]+"条");
  667. }
  668. return null;
  669. }
  670. });
  671. }
  672. static void Query_DebugReadWKT() throws Exception {
  673. System.out.println("========== Debug: 读取边界网格划分图形WKT文本数据 ==========");
  674. System.out.println("调试用的,读取已在wkbs结构化文件中保存的网格划分图形WKT数据,用于核对网格划分情况。");
  675. System.out.println("读取到的wkt文本,可以直接粘贴到页面内渲染显示:https://xiangyuecn.github.io/AreaCity-JsSpider-StatsGov/assets/geo-echarts.html");
  676. System.out.println();
  677. ExtPathExpIn("GirdWKT", new Func<Test.ExtPathExpInArgs, Object>() {
  678. @Override
  679. public Object Exec(ExtPathExpInArgs args) throws Exception {
  680. int[] count=new int[] { 0 };
  681. Current.Debug_ReadGeometryGridSplitsWKT("", null, new Func<String, Boolean>() {
  682. @Override
  683. public Boolean Exec(String prop) throws Exception {
  684. return ExtPathMatch(prop, args.extPath_exp);
  685. }
  686. }, getWktReadFn("GirdWKT", args, count));
  687. if(count[0] == 0) {
  688. System.out.println("未找到“"+args.extPath_inputTxt+"”匹配的边界!");
  689. } else {
  690. System.out.println("查找完成,共"+count[0]+"条");
  691. }
  692. return null;
  693. }
  694. });
  695. }
  696. static Func<String[], Boolean> getWktReadFn(String tag, ExtPathExpInArgs args, int[] count){
  697. return new Func<String[], Boolean>() {
  698. @Override
  699. public Boolean Exec(String[] val) throws Exception {
  700. count[0]++;
  701. if(args.outFile!=null) {
  702. String str=val[0] +"\t"+ val[1]+"\n";
  703. byte[] bytes=str.getBytes("utf-8");
  704. args.outFile.write(bytes);
  705. System.out.println(count[0]+"条"+tag+"属性:"+val[0]);
  706. System.out.println(" "+bytes.length+"字节已保存到文件:"+args.outFilePath);
  707. } else {
  708. String str=count[0]+"条"+tag+"属性:"+val[0];
  709. if(val[1].length()>500) {
  710. System.out.println(str);
  711. str=" WKT超长未显示("+val[1].length()+"字节),请命令后面输入\" > 文件名\"输出到文件";
  712. } else {
  713. str=str +"\t"+ val[1];
  714. }
  715. System.out.println(str);
  716. }
  717. return false;
  718. }
  719. };
  720. }
  721. static boolean ExtPathMatch(String prop, String exp) {
  722. int i0=prop.indexOf("ext_path");
  723. if(i0==-1)return false;
  724. int i1=prop.indexOf(",", i0);
  725. if(i1==-1)i1=prop.length();
  726. return prop.substring(i0+9, i1).contains(exp);
  727. }
  728. static class ExtPathExpInArgs{
  729. String extPath_exp;
  730. String extPath_inputTxt;
  731. String outFilePath;
  732. FileOutputStream outFile;
  733. }
  734. static void ExtPathExpIn(String tag, Func<ExtPathExpInArgs, Object> fn) throws Exception {
  735. String idxs=GetInitInstanceIdxs();
  736. System.out.println("请输入"+tag+"要查询的城市完整名称,为ext_path值:");
  737. System.out.println(" - 如:“湖北省 武汉市 洪山区”,精确查找");
  738. System.out.println(" - 如:“*武汉市*”,*通配符模糊查找");
  739. System.out.println(" - 如:“*”,查找全部");
  740. System.out.println(" - 结尾输入“ > 文件名”可保存到文件");
  741. System.out.println(" - 输入 exit 退出,输入 use "+idxs+" 切换查询实例");
  742. while(true){
  743. System.out.print((idxs.length()<2?"":"[实例"+CurrentIdx+"] ")+"> ");
  744. String inStr=ReadIn().trim();
  745. if(inStr.length()==0) {
  746. System.out.println("输入为空,请重新输入!如需退出请输入exit");
  747. continue;
  748. }
  749. if(inStr.equals("exit")) {
  750. System.out.println("bye! 已退出读取。");
  751. System.out.println();
  752. return;
  753. }
  754. if(inStr.startsWith("use ")) {
  755. if(InTxt_SetCurrent(inStr, true)) {
  756. System.out.println("已设置当前实例为"+CurrentIdx);
  757. }
  758. continue;
  759. }
  760. ExtPathExpInArgs args=new ExtPathExpInArgs();
  761. String[] ins=inStr.split(" > ");
  762. args.extPath_inputTxt=ins[0];
  763. String outFilePath= ins.length>1?ins[1]:"";
  764. FileOutputStream outFile=null;
  765. if(outFilePath.length()>0) {
  766. outFilePath=new File(outFilePath).getAbsolutePath();
  767. outFile=new FileOutputStream(outFilePath);
  768. }
  769. args.outFilePath=outFilePath;
  770. args.outFile=outFile;
  771. String exp=ins[0];
  772. if(exp.equals("*")) {
  773. exp="";
  774. } else {
  775. if(exp.startsWith("*")) {
  776. exp=exp.substring(1);
  777. }else {
  778. exp="\""+exp;
  779. }
  780. if(exp.endsWith("*")) {
  781. exp=exp.substring(0, exp.length()-1);
  782. }else {
  783. exp=exp+"\"";
  784. }
  785. }
  786. args.extPath_exp=exp;
  787. fn.Exec(args);
  788. if(outFile!=null)outFile.close();
  789. }
  790. }
  791. static public boolean StartHttpApiServer(int port) throws Exception {
  792. String clazzName=Test.class.getPackage().getName()+".Test_HttpApiServer";
  793. Method[] fns;
  794. try {
  795. fns=Class.forName(clazzName).getMethods();
  796. }catch (Exception e) {
  797. System.out.println("Test_HttpApiServer.java加载失败,不支持启动本地轻量HTTP API服务。");
  798. return false;
  799. }
  800. Method fn=null; for(Method x : fns) if(x.getName().equals("Create")) fn=x;
  801. return (boolean)fn.invoke(null, "0.0.0.0", port);
  802. }
  803. static public String ReadIn() throws Exception {
  804. ByteArrayOutputStream in=new ByteArrayOutputStream();
  805. while(true) {
  806. int byt=System.in.read();
  807. if(byt=='\r') continue;
  808. if(byt=='\n') {
  809. break;
  810. }
  811. if(in.size()>=2048) {//防止内存溢出,某些环境下可能会有无限的输入
  812. byte[] bytes=in.toByteArray();
  813. in=new ByteArrayOutputStream();
  814. in.write(bytes, bytes.length-1024, 1024);
  815. }
  816. in.write(byt);
  817. }
  818. return in.toString();
  819. }
  820. static public boolean InTxt_SetCurrent(String inTxt, boolean checkInit) {
  821. int idx=-1; try{ idx=Integer.parseInt(inTxt.split("\\s+")[1]); }catch(Exception e) {}
  822. if(idx<0 || idx>=AreaCityQuery.Instances.length) {
  823. System.out.println("use实例序号无效,请重新输入!");
  824. return false;
  825. }
  826. AreaCityQuery val=AreaCityQuery.Instances[idx];
  827. if(checkInit && val.GetInitStatus()!=2) {
  828. System.out.println("实例"+idx+"未初始化,可用实例["+GetInitInstanceIdxs()+"],请重新输入!");
  829. return false;
  830. }
  831. CurrentIdx=idx;
  832. Current=val;
  833. return true;
  834. }
  835. static public String GetInitInstanceIdxs() {
  836. String initIdxs="";
  837. for(int i=0;i<AreaCityQuery.Instances.length;i++) {
  838. AreaCityQuery item=AreaCityQuery.Instances[i];
  839. if(item.GetInitStatus()==2) {
  840. initIdxs+=(initIdxs.length()>0?",":"")+i;
  841. }
  842. }
  843. return initIdxs;
  844. }
  845. static boolean IsCmd=false;
  846. static String HR="-----------------------------------";
  847. static void Start(String[] args) throws Exception {
  848. if(args.length>0) {
  849. System.out.print(args.length+"个启动参数");
  850. for(int i=0;i<args.length;i++) {
  851. if(args[i].equals("-cmd")) {
  852. IsCmd=true;
  853. }
  854. System.out.print(",参数"+(i+1)+":"+args[i]);
  855. }
  856. System.out.println(IsCmd?",已进入命令行模式。":"");
  857. System.out.println();
  858. }
  859. while(true) {
  860. boolean isInit=Current.GetInitStatus()==2;
  861. ArrayList<Integer> idxs=new ArrayList<>();
  862. int curIdx=0; String initIdxs="";
  863. for(int i=0;i<AreaCityQuery.Instances.length;i++) {
  864. AreaCityQuery item=AreaCityQuery.Instances[i];
  865. if(item.GetInitStatus()==2) {
  866. idxs.add(i);
  867. initIdxs+=(initIdxs.length()>0?",":"")+i;
  868. }
  869. if(item==Current)curIdx=i;
  870. }
  871. System.out.println("【功能菜单】 当前静态实例Instances["+curIdx+"]");
  872. System.out.println("1. "+(isInit?"重新":"")+"初始化:调用 Init_StoreInWkbsFile -内存占用很低(性能受IO限制)"+(Current.IsStoreInWkbsFile()?" [已初始化]":""));
  873. System.out.println("2. "+(isInit?"重新":"")+"初始化:调用 Init_StoreInMemory -内存占用和json文件差不多大(性能豪放)"+(Current.IsStoreInMemory()?" [已初始化]":""));
  874. if(isInit) {
  875. System.out.println(HR);
  876. System.out.println("3. 测试:基础功能测试");
  877. System.out.println("4. 测试:1万个伪随机点测试");
  878. System.out.println("5. 测试:多线程性能测试");
  879. System.out.println(HR);
  880. System.out.println("6. 查询: QueryPoint 查找坐标点所在省市区乡镇");
  881. System.out.println("A. 查询: QueryGeometry 查找和图形相交的省市区乡镇");
  882. System.out.println("7. 查询: ReadWKT 读取省市区乡镇边界的WKT文本数据");
  883. System.out.println("8. 查询: Debug 读取边界网格划分图形WKT文本数据");
  884. System.out.println(HR);
  885. System.out.println("9. HTTP: 启动本地轻量HTTP API服务");
  886. }
  887. System.out.println(HR);
  888. System.out.println("*. 输入 use 0-"+(AreaCityQuery.Instances.length-1)+" 切换静态实例,list 列出实例信息,当前"+(initIdxs.length()==0?"无已初始化实例":"["+initIdxs+"]已初始化")+"");
  889. System.out.println("*. 输入 exit 退出");
  890. System.out.println();
  891. System.out.println("请输入菜单序号:");
  892. System.out.print("> ");
  893. boolean waitAnyKey=true;
  894. String inTxt="";
  895. while(true) {
  896. int byt=System.in.read();
  897. inTxt+=(char)byt;
  898. if(byt!='\n') {
  899. continue;
  900. }
  901. inTxt=inTxt.trim().toUpperCase();
  902. try {
  903. if(inTxt.equals("1") || inTxt.equals("2")) {
  904. Current.ResetInitStatus();
  905. System.gc();
  906. Init_FromMenu(inTxt.equals("1"));
  907. if(Current.GetInitStatus()==2) {
  908. waitAnyKey=false;
  909. }
  910. } else if(isInit && inTxt.equals("3")) {
  911. for(int i : idxs) {
  912. if(idxs.size()>1) System.out.println("【测试实例"+i+"】 Instances["+i+"]");
  913. BaseTest(i);
  914. }
  915. } else if(isInit && inTxt.equals("4")) {
  916. for(int i : idxs) {
  917. if(idxs.size()>1) System.out.println("【测试实例"+i+"】 Instances["+i+"]");
  918. LargeRndPointTest(i);
  919. }
  920. } else if(isInit && inTxt.equals("5")) {
  921. ThreadRun(idxs);
  922. waitAnyKey=false;
  923. } else if(isInit && inTxt.equals("6")) {
  924. Query_Point();
  925. waitAnyKey=false;
  926. } else if(isInit && inTxt.equals("A")) {
  927. Query_Geometry();
  928. waitAnyKey=false;
  929. } else if(isInit && inTxt.equals("7")) {
  930. Read_WKT();
  931. waitAnyKey=false;
  932. } else if(isInit && inTxt.equals("8")) {
  933. Query_DebugReadWKT();
  934. waitAnyKey=false;
  935. } else if(isInit && inTxt.equals("9")) {
  936. if(StartHttpApiServer(HttpApiServerPort)) {
  937. waitAnyKey=false;
  938. }
  939. } else if(inTxt.startsWith("USE ")) {
  940. if(InTxt_SetCurrent(inTxt, false)) {
  941. waitAnyKey=false;
  942. }else {
  943. inTxt="";
  944. System.out.print("> ");
  945. continue;
  946. }
  947. } else if(inTxt.equals("LIST")) {
  948. for(int i : idxs) {
  949. AreaCityQuery item=AreaCityQuery.Instances[i];
  950. QueryInitInfo info=item.GetInitInfo();
  951. System.out.println((item==Current?"[当前]":"")
  952. +"实例"+i+": Instances["+i+"] "+(item.IsStoreInMemory()?"Init_StoreInMemory":"Init_StoreInWkbsFile"));
  953. System.out.println(" Geometry "+info.GeometryCount+" 个(Grid切分Polygon "+info.PolygonCount+" 个)");
  954. System.out.println(" Data文件: "+info.FilePath_Data);
  955. System.out.println(" Wkbs文件: "+info.FilePath_SaveWkbs);
  956. }
  957. if(idxs.size()==0) {
  958. System.out.println("没有已初始化的实例信息!");
  959. }
  960. } else if(inTxt.equals("EXIT")) {
  961. System.out.println("bye!");
  962. return;
  963. } else {
  964. inTxt="";
  965. System.out.println("序号无效,请重新输入菜单序号!");
  966. System.out.print("> ");
  967. continue;
  968. }
  969. } catch(Exception e) {
  970. e.printStackTrace();
  971. }
  972. break;
  973. }
  974. if(waitAnyKey) {
  975. System.out.println("按任意键继续...");
  976. int n=System.in.read();
  977. if(n=='\r') {
  978. System.in.read();
  979. }
  980. }
  981. }
  982. }
  983. }