├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── contour │ │ ├── algorithm │ │ ├── IDW.java │ │ ├── KDTree.java │ │ └── Kriging.java │ │ ├── bean │ │ ├── DrawStyle.java │ │ ├── Tuple2.java │ │ ├── Tuple3.java │ │ └── Tuple5.java │ │ ├── draw │ │ ├── Contour.java │ │ └── spatial │ │ │ ├── Border.java │ │ │ ├── BorderLine.java │ │ │ ├── BorderPoint.java │ │ │ ├── EndPoint.java │ │ │ ├── Extent.java │ │ │ ├── IJPoint.java │ │ │ ├── LPolygon.java │ │ │ ├── LegendPara.java │ │ │ ├── Line.java │ │ │ ├── PointD.java │ │ │ ├── PointF.java │ │ │ ├── PolyLine.java │ │ │ └── Polygon.java │ │ └── utils │ │ ├── CsvParser.java │ │ ├── EncodeUtils.java │ │ ├── MathsUtil.java │ │ └── SphericalMercator.java └── resources │ └── log4j.properties └── test ├── java ├── color │ └── ColorTest.java ├── contour │ ├── IDW │ │ ├── IDWImage.java │ │ ├── IDWTest.java │ │ └── IDWutil.java │ ├── common │ │ └── AbstractImage.java │ ├── kriging │ │ ├── KrigingImage.java │ │ └── KrigingTest.java │ └── utils │ │ ├── ColorUtils.java │ │ ├── FileUtils.java │ │ ├── InterpolateRgb.java │ │ ├── MapUtils.java │ │ └── PakoGzipUtils.java └── graphics │ └── GraphicsTest.java └── resources └── input ├── border.csv └── data.csv /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | *target 22 | *.vscode 23 | *.settings* 24 | .project 25 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 26 | hs_err_pid* 27 | 28 | !/lib/*.jar -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 David Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jcontour 2 | 3 | jcontour is a Java-based project about plotting meteorological contours. It integrates inverse distance weighting (IDW) and ordinary kriging interpolation algorithms, providing tools for visualizing and analyzing meteorological data. 4 | 5 | ## Features 6 | 7 | - **Inverse Distance Weighting (IDW) Interpolation**: This algorithm estimates unknown data values based on known points, using inverse distance weighting. 8 | - **Ordinary Kriging Interpolation**: A geostatistical method that considers the spatial correlation between known points to estimate unknown data values. The kriging algorithm in this project references the open-source library [oeo4b/kriging](https://github.com/oeo4b/kriging). 9 | - **Geometric Drawing Calculations**: For contour plotting and related display calculations, I utilize code borrowed from [meteoinfo/wContour](https://github.com/meteoinfo/wContour), specifically dealing with KD-TREE and POLYGON functionalities. 10 | 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | jcontour 7 | jcontour 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 1.8 12 | 13 | 14 | 15 | 16 | org.slf4j 17 | slf4j-log4j12 18 | 1.7.25 19 | 20 | 21 | org.apache.commons 22 | commons-csv 23 | 1.0 24 | 25 | 26 | junit 27 | junit 28 | 4.12 29 | test 30 | 31 | 32 | com.alibaba 33 | fastjson 34 | 1.2.67 35 | test 36 | 37 | 38 | 39 | 40 | jcontour-1.0.0-SNAPSHOT 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-compiler-plugin 46 | 3.1 47 | 48 | ${java.version} 49 | ${java.version} 50 | UTF-8 51 | true 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-dependency-plugin 59 | 3.0.1 60 | 61 | 62 | copy-dependencies 63 | package 64 | 65 | copy-dependencies 66 | 67 | 68 | ${project.build.directory}/lib 69 | false 70 | false 71 | true 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-surefire-plugin 80 | 2.5 81 | 82 | true 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-jar-plugin 89 | 2.4 90 | 91 | 92 | 93 | false 94 | 95 | 96 | 97 | true 98 | 101 | lib/ 102 | 103 | 104 | 105 | 106 | 107 | **/assembly/ 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/java/contour/algorithm/IDW.java: -------------------------------------------------------------------------------- 1 | package contour.algorithm; 2 | 3 | import java.awt.geom.Point2D; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | 9 | import contour.algorithm.KDTree.Euclidean; 10 | 11 | /** 12 | * Interpolate class - including the functions of interpolation 13 | * 14 | * @author Yaqiang Wang 15 | * @version $Revision: 1.6 $ 16 | */ 17 | public class IDW { 18 | // 19 | /** 20 | * Create grid x/y coordinate arrays with x/y delt 21 | * 22 | * @param Xlb x of left-bottom 23 | * @param Ylb y of left-bottom 24 | * @param Xrt x of right-top 25 | * @param Yrt y of right-top 26 | * @param XDelt x delt 27 | * @param YDelt y delt 28 | * @return X/Y coordinate arrays 29 | */ 30 | public static List createGridXY_Delt(double Xlb, double Ylb, double Xrt, double Yrt, double XDelt, double YDelt) { 31 | int i, Xnum, Ynum; 32 | Xnum = (int) ((Xrt - Xlb) / XDelt + 1); 33 | Ynum = (int) ((Yrt - Ylb) / YDelt + 1); 34 | double[] X = new double[Xnum]; 35 | double[] Y = new double[Ynum]; 36 | for (i = 0; i < Xnum; i++) { 37 | X[i] = Xlb + i * XDelt; 38 | } 39 | for (i = 0; i < Ynum; i++) { 40 | Y[i] = Ylb + i * YDelt; 41 | } 42 | 43 | List values = new ArrayList<>(); 44 | values.add(X); 45 | values.add(Y); 46 | return values; 47 | } 48 | 49 | /** 50 | * Create grid X/Y coordinate 51 | * 52 | * @param Xlb X left bottom 53 | * @param Ylb Y left bottom 54 | * @param Xrt X right top 55 | * @param Yrt Y right top 56 | * @param X X coordinate 57 | * @param Y Y coordinate 58 | */ 59 | public static void createGridXY_Num(double Xlb, double Ylb, double Xrt, double Yrt, 60 | double[] X, double[] Y) { 61 | int i; 62 | double XDelt, YDelt; 63 | int Xnum = X.length; 64 | int Ynum = Y.length; 65 | XDelt = (Xrt - Xlb) / Xnum; 66 | YDelt = (Yrt - Ylb) / Ynum; 67 | for (i = 0; i < Xnum; i++) { 68 | X[i] = Xlb + i * XDelt; 69 | } 70 | for (i = 0; i < Ynum; i++) { 71 | Y[i] = Ylb + i * YDelt; 72 | } 73 | System.out.println("像元XDelt"+XDelt); 74 | System.out.println("像元YDelt"+YDelt); 75 | } 76 | 77 | static List> searchPointsInRange(double[][] sCoords, 78 | double[] center, 79 | double longSemiAxisWidth, 80 | double shortSemiAxisWidth) { 81 | List allPoints = new ArrayList<>(); 82 | List inRangePoints = new ArrayList<>(); 83 | double distance; 84 | double ssawSquare = shortSemiAxisWidth * shortSemiAxisWidth; 85 | double lsawSquare = longSemiAxisWidth * longSemiAxisWidth; 86 | 87 | for(int i = 0, len = sCoords.length; i < len; i++) { 88 | double px = sCoords[i][0]; 89 | double py = sCoords[i][1]; 90 | distance = Math.pow(center[0]-px, 2) + Math.pow(center[1]-py, 2); 91 | if(ssawSquare*(px-center[0])*(px-center[0]) + lsawSquare*(py-center[1])*(py-center[1]) 92 | < ssawSquare*lsawSquare){ 93 | inRangePoints.add(new Object[]{ 94 | sCoords[i], 95 | 1/distance 96 | }); 97 | } 98 | allPoints.add(new Object[]{ 99 | sCoords[i], 100 | 1/distance 101 | }); 102 | 103 | } 104 | 105 | allPoints.sort(new Comparator(){ 106 | @Override 107 | public int compare(Object[] o1, Object[] o2) { 108 | return Double.compare((double)o2[1], (double)o1[1]); 109 | } 110 | }); 111 | inRangePoints.sort(new Comparator(){ 112 | @Override 113 | public int compare(Object[] o1, Object[] o2) { 114 | return Double.compare((double)o2[1], (double)o1[1]); 115 | } 116 | }); 117 | 118 | return Arrays.asList(inRangePoints, allPoints); 119 | } 120 | 121 | 122 | /** 123 | * 124 | * @param SCoords 125 | * @param X 126 | * @param Y 127 | * @param maxNumberOfNearestNeighbors 最大相邻要素数 128 | * @param minNumberOfNearestNeighbors 最小相邻要素数 129 | * @param longSemiAxisWidth 长半轴长度 130 | * @param shortSemiAxisWidth 短半轴长度 131 | * @param sectorNum 扇区数量 132 | * @param sectorOffsetDegree 扇区偏移角度 133 | * @return 134 | */ 135 | public static double[][] interpolation_IDW(double[][] SCoords, 136 | double[]X, double[]Y, 137 | int maxNumberOfNearestNeighbors, 138 | int minNumberOfNearestNeighbors, 139 | double longSemiAxisWidth, 140 | double shortSemiAxisWidth, 141 | int sectorNum, 142 | int sectorOffsetDegree) { 143 | int rowNum, colNum, pNum; 144 | colNum = X.length; 145 | rowNum = Y.length; 146 | pNum = SCoords.length; 147 | double[][] GCoords = new double[rowNum][colNum]; 148 | int i, j, p; 149 | double SV, SW; 150 | 151 | for(i = 0; i < rowNum; i++) { 152 | for(j = 0; j < colNum; j++) { 153 | GCoords[i][j] = Double.MIN_VALUE; 154 | SV = 0; 155 | SW = 0; 156 | for(p = 0; p> pointsList = searchPointsInRange(SCoords, 165 | new double[]{X[i], Y[j]}, 166 | longSemiAxisWidth, 167 | shortSemiAxisWidth); 168 | List inRangePoints = pointsList.get(0); 169 | List allPoints = pointsList.get(1); 170 | List needPoints = null; 171 | if(inRangePoints.size()>=maxNumberOfNearestNeighbors) {//大于等于最大相邻要素数 172 | needPoints = inRangePoints.subList(0, maxNumberOfNearestNeighbors); 173 | }else if(inRangePoints.size()>=minNumberOfNearestNeighbors) {//大于等于最小相邻要素数,小于最大相邻要素数 174 | needPoints = inRangePoints; 175 | }else { //小于最小相邻要素数 176 | if(allPoints.size()>=minNumberOfNearestNeighbors) { 177 | needPoints = allPoints.subList(0, minNumberOfNearestNeighbors); 178 | } else { 179 | needPoints = allPoints; 180 | } 181 | } 182 | 183 | int len = needPoints.size(); 184 | Object[] point; 185 | double weight; 186 | double[]scoord; 187 | for (p = 0; p < len; p++) { 188 | point = needPoints.get(p); 189 | scoord = (double[])point[0]; 190 | weight = (double) point[1]; 191 | SV += weight * scoord[2]; 192 | SW += weight; 193 | } 194 | GCoords[i][j] = SV / SW; 195 | } 196 | } 197 | } 198 | 199 | return GCoords; 200 | } 201 | 202 | /** 203 | * Interpolation with IDW neighbor method 204 | * 205 | * @param SCoords discrete data array 206 | * @param X grid X array 207 | * @param Y grid Y array 208 | * @param NumberOfNearestNeighbors number of nearest neighbors 209 | * @return grid data 210 | */ 211 | public static double[][] interpolation_IDW_Neighbor(double[][] SCoords, double[] X, double[] Y, 212 | int NumberOfNearestNeighbors) { 213 | int rowNum, colNum, pNum; 214 | colNum = X.length; 215 | rowNum = Y.length; 216 | pNum = SCoords.length; 217 | double[][] GCoords = new double[rowNum][colNum]; 218 | int i, j, p, l, aP; 219 | double w, SV, SW, aMin; 220 | int points = NumberOfNearestNeighbors; 221 | Object[][] NW = new Object[2][points]; 222 | 223 | //---- Do interpolation 224 | for (i = 0; i < rowNum; i++) { 225 | for (j = 0; j < colNum; j++) { 226 | GCoords[i][j] = Double.MIN_VALUE; 227 | SV = 0; 228 | SW = 0; 229 | for (p = 0; p < points; p++) {//先取样本前points个点计算反距离和记录相应的位置所有p 230 | if (X[j] == SCoords[p][0] && Y[i] == SCoords[p][1]) { 231 | GCoords[i][j] = SCoords[p][2]; 232 | break; 233 | } else { 234 | w = 1 / (Math.pow(X[j] - SCoords[p][0], 2) + Math.pow(Y[i] - SCoords[p][1], 2)); 235 | NW[0][p] = w;//记录权重 236 | NW[1][p] = p;//记录位置索引 237 | } 238 | } 239 | if (GCoords[i][j] == Double.MIN_VALUE) { 240 | for (p = points; p < pNum; p++) { 241 | double distance = Math.pow(X[j] - SCoords[p][0], 2) + Math.pow(Y[i] - SCoords[p][1], 2); 242 | if (distance == 0) { 243 | GCoords[i][j] = SCoords[p][2]; 244 | break; 245 | } else { 246 | aMin = Double.parseDouble(NW[0][0].toString()); 247 | aP = 0; 248 | for (l = 1; l < points; l++) {//找到前points个样本点中的最小权重值(距离倒数) 249 | if (Double.parseDouble(NW[0][l].toString()) < aMin) { 250 | aMin = Double.parseDouble(NW[0][l].toString()); 251 | aP = l; 252 | } 253 | } 254 | w = 1 / distance; 255 | if (w > aMin) {//不断用权值大的点替换前points个点,最终实现前point个点取的是最邻近的点 256 | NW[0][aP] = w; 257 | NW[1][aP] = p; 258 | } 259 | } 260 | } 261 | if (GCoords[i][j] == Double.MIN_VALUE) { 262 | /** 263 | * 根据选取的邻近的样本点计算预测的的值 264 | * z = ∑zi*(1/di) / ∑(1/di) 265 | */ 266 | for (p = 0; p < points; p++) { 267 | SV += Double.parseDouble(NW[0][p].toString()) * SCoords[Integer.parseInt(NW[1][p].toString())][2]; 268 | SW += Double.parseDouble(NW[0][p].toString()); 269 | } 270 | GCoords[i][j] = SV / SW; 271 | } 272 | } 273 | } 274 | } 275 | 276 | //---- Smooth with 5 points 277 | double s = 0.5; 278 | for (i = 1; i < rowNum - 1; i++) { 279 | for (j = 1; j < colNum - 1; j++) { 280 | GCoords[i][j] = GCoords[i][j] + s / 4 * (GCoords[i + 1][j] + GCoords[i - 1][j] 281 | + GCoords[i][j + 1] + GCoords[i][j - 1] - 4 * GCoords[i][j]); 282 | } 283 | } 284 | 285 | return GCoords; 286 | } 287 | 288 | /** 289 | * Interpolation with IDW neighbor method 290 | * 291 | * @param SCoords discrete data array 292 | * @param X grid X array 293 | * @param Y grid Y array 294 | * @param NumberOfNearestNeighbors number of nearest neighbors 295 | * @param unDefData undefine data 296 | * @return interpolated grid data 297 | */ 298 | public static double[][] interpolation_IDW_Neighbor(double[][] SCoords, double[] X, double[] Y, 299 | int NumberOfNearestNeighbors, double unDefData) { 300 | int rowNum, colNum, pNum; 301 | colNum = X.length; 302 | rowNum = Y.length; 303 | pNum = SCoords.length; //sample data length 304 | double[][] GCoords = new double[rowNum][colNum]; 305 | int i, j, p, l, aP; 306 | double w, SV, SW, aMin; 307 | double[] AllWeights = new double[pNum]; 308 | double[][] NW = new double[2][NumberOfNearestNeighbors];//存储K近邻样本权值和权值索引 309 | int NWIdx; 310 | 311 | //---- Do interpolation with IDW method 312 | for (i = 0; i < rowNum; i++) { 313 | for (j = 0; j < colNum; j++) { 314 | GCoords[i][j] = unDefData; 315 | SV = 0; 316 | SW = 0; 317 | NWIdx = 0; 318 | 319 | //check 320 | // if(x[j]-SCoords[pNum-1][1]) 321 | 322 | for (p = 0; p < pNum; p++) {//遍历样本数据 323 | if (SCoords[p][2] == unDefData) { 324 | AllWeights[p] = -1; 325 | continue; 326 | } 327 | if (X[j] == SCoords[p][0] && Y[i] == SCoords[p][1]) { 328 | GCoords[i][j] = SCoords[p][2]; 329 | break; 330 | } else { 331 | w = 1 / (Math.pow(X[j] - SCoords[p][0], 2) + Math.pow(Y[i] - SCoords[p][1], 2)); 332 | AllWeights[p] = w; 333 | if (NWIdx < NumberOfNearestNeighbors) {//暂时取前NumberOfNearestNeighbors个计算的权重值和索引 334 | NW[0][NWIdx] = w; //权重值 335 | NW[1][NWIdx] = p; //权重值在AllWeights中的索引 336 | } 337 | NWIdx += 1; 338 | } 339 | } 340 | 341 | if (GCoords[i][j] == unDefData) { 342 | for (p = 0; p < pNum; p++) {//将NW中的n个权重替换成AllWeights中n个较大的值 343 | w = AllWeights[p]; 344 | if (w == -1) { 345 | continue; 346 | } 347 | 348 | aMin = NW[0][0]; 349 | aP = 0; 350 | for (l = 1; l < NumberOfNearestNeighbors; l++) {//求NW中的最小权重值aMin和索引aP 351 | if ((double) NW[0][l] < aMin) { 352 | aMin = (double) NW[0][l]; 353 | aP = l; 354 | } 355 | } 356 | if (w > aMin) { 357 | NW[0][aP] = w; 358 | NW[1][aP] = p; 359 | } 360 | } 361 | for (p = 0; p < NumberOfNearestNeighbors; p++) { 362 | SV += (double) NW[0][p] * SCoords[(int) NW[1][p]][2];//加权值求和 363 | SW += (double) NW[0][p]; //权重和 364 | } 365 | GCoords[i][j] = SV / SW; 366 | } 367 | } 368 | } 369 | 370 | //---- Smooth with 5 points 371 | double s = 0.5; 372 | for (i = 1; i < rowNum - 1; i++) { 373 | for (j = 1; j < colNum - 1; j++) { 374 | GCoords[i][j] = GCoords[i][j] + s / 4 * (GCoords[i + 1][j] + GCoords[i - 1][j] + GCoords[i][j + 1] 375 | + GCoords[i][j - 1] - 4 * GCoords[i][j]); 376 | } 377 | 378 | } 379 | 380 | return GCoords; 381 | } 382 | 383 | /** 384 | * Interpolation with IDW radius method 385 | * 386 | * @param SCoords discrete data array 387 | * @param X grid X array 388 | * @param Y grid Y array 389 | * @param NeededPointNum needed at least point number 390 | * @param radius search radius 391 | * @param unDefData undefine data 392 | * @return interpolated grid data 393 | */ 394 | public static double[][] interpolation_IDW_Radius(double[][] SCoords, double[] X, double[] Y, 395 | int NeededPointNum, double radius, double unDefData) { 396 | int rowNum, colNum, pNum; 397 | colNum = X.length; 398 | rowNum = Y.length; 399 | pNum = SCoords.length; 400 | double[][] GCoords = new double[rowNum][colNum]; 401 | int i, j, p, vNum; 402 | double w, SV, SW; 403 | boolean ifPointGrid; 404 | 405 | //---- Do interpolation 406 | for (i = 0; i < rowNum; i++) { 407 | for (j = 0; j < colNum; j++) { 408 | GCoords[i][j] = unDefData; 409 | ifPointGrid = false; 410 | SV = 0; 411 | SW = 0; 412 | vNum = 0; 413 | for (p = 0; p < pNum; p++) { 414 | if (SCoords[p][2] == unDefData) { 415 | continue; 416 | } 417 | 418 | if (SCoords[p][0] < X[j] - radius || SCoords[p][0] > X[j] + radius || SCoords[p][1] < Y[i] - radius 419 | || SCoords[p][1] > Y[i] + radius) { 420 | continue; 421 | } 422 | 423 | if (X[j] == SCoords[p][0] && Y[i] == SCoords[p][1]) { 424 | GCoords[i][j] = SCoords[p][2]; 425 | ifPointGrid = true; 426 | break; 427 | } else if (Math.sqrt(Math.pow(X[j] - SCoords[p][0], 2) 428 | + Math.pow(Y[i] - SCoords[p][1], 2)) <= radius) { 429 | w = 1 / (Math.pow(X[j] - SCoords[p][0], 2) + Math.pow(Y[i] - SCoords[p][1], 2)); 430 | SW = SW + w; 431 | SV = SV + SCoords[p][2] * w; 432 | vNum += 1; 433 | } 434 | } 435 | 436 | if (!ifPointGrid) { 437 | if (vNum >= NeededPointNum) { 438 | GCoords[i][j] = SV / SW; 439 | } 440 | } 441 | } 442 | } 443 | 444 | //---- Smooth with 5 points 445 | double s = 0.5; 446 | for (i = 1; i < rowNum - 1; i++) { 447 | for (j = 1; j < colNum - 2; j++) { 448 | if (GCoords[i][j] == unDefData || GCoords[i + 1][j] == unDefData || GCoords[i - 1][j] 449 | == unDefData || GCoords[i][j + 1] == unDefData || GCoords[i][j - 1] == unDefData) { 450 | continue; 451 | } 452 | GCoords[i][j] = GCoords[i][j] + s / 4 * (GCoords[i + 1][j] + GCoords[i - 1][j] + GCoords[i][j + 1] 453 | + GCoords[i][j - 1] - 4 * GCoords[i][j]); 454 | } 455 | } 456 | 457 | return GCoords; 458 | } 459 | 460 | /** 461 | * Interpolation with IDW radius method - using KDTree for fast search 462 | * 463 | * @param stData discrete data array 464 | * @param xGrid grid X array 465 | * @param yGrid grid Y array 466 | * @param neededPointNum needed at least point number 467 | * @param radius search radius 468 | * @param fillValue Fill value 469 | * @return interpolated grid data 470 | */ 471 | public static double[][] idw_Radius_kdTree(double[][] stData, double[] xGrid, double[] yGrid, 472 | int neededPointNum, double radius, double fillValue) { 473 | Euclidean kdtree = new Euclidean<>(2); 474 | int l = stData.length; 475 | 476 | for (int i = 0; i < l; i++) { 477 | //Avoid key repeat 478 | double nodex = stData[i][0] + (Math.random() * 10e-5); 479 | double nodey = stData[i][1] + (Math.random() * 10e-5); 480 | kdtree.addPoint(new double[]{nodex, nodey}, new double[]{stData[i][0], stData[i][1], stData[i][2]}); 481 | } 482 | 483 | int w = xGrid.length; 484 | int h = yGrid.length; 485 | 486 | double[][] grid = new double[h][w]; 487 | for (int i = 0; i < h; i++) { 488 | double yValue = yGrid[i]; 489 | for (int j = 0; j < w; j++) { 490 | double xValue = xGrid[j]; 491 | List nearPntList = kdtree.ballSearch(new double[]{xValue, yValue}, radius); 492 | int nearSize = nearPntList.size(); 493 | if (nearSize < neededPointNum) { 494 | grid[i][j] = fillValue; 495 | continue; 496 | } 497 | 498 | double z_sum = 0.0; 499 | double weight_sum = 0.0; 500 | for (int k = 0; k < nearSize; k++) { 501 | double[] xyz = nearPntList.get(k); 502 | double distance = Point2D.Double.distance(xValue, yValue, xyz[0], xyz[1]); 503 | if (distance <= radius && distance > 0) { 504 | weight_sum += radius / distance; 505 | z_sum += radius / distance * xyz[2]; 506 | } else if (Math.abs(distance) < 0.0001) { //Using point value when the grid is point 507 | z_sum = xyz[2]; 508 | weight_sum = 1.0f; 509 | break; 510 | } 511 | } 512 | if (Math.abs(weight_sum) < 0.0001) { 513 | grid[i][j] = fillValue; 514 | } else { 515 | grid[i][j] = z_sum / weight_sum; 516 | } 517 | } 518 | } 519 | return grid; 520 | } 521 | 522 | /** 523 | * Interpolate from grid data 524 | * 525 | * @param GridData input grid data 526 | * @param X input x coordinates 527 | * @param Y input y coordinates 528 | * @param unDefData undefine data 529 | * @param nX output x coordinate 530 | * @param nY output y coordinate 531 | * @return output grid data 532 | */ 533 | public static double[][] interpolation_Grid(double[][] GridData, double[] X, double[] Y, double unDefData, 534 | double[] nX, double[] nY) { 535 | int nxNum = X.length * 2 - 1; 536 | int nyNum = Y.length * 2 - 1; 537 | nX = new double[nxNum]; 538 | nY = new double[nyNum]; 539 | double[][] nGridData = new double[nyNum][nxNum]; 540 | int i, j; 541 | double a, b, c, d; 542 | List dList; 543 | for (i = 0; i < nxNum; i++) { 544 | if (i % 2 == 0) { 545 | nX[i] = X[i / 2]; 546 | } else { 547 | nX[i] = (X[(i - 1) / 2] + X[(i - 1) / 2 + 1]) / 2; 548 | } 549 | } 550 | for (i = 0; i < nyNum; i++) { 551 | if (i % 2 == 0) { 552 | nY[i] = Y[i / 2]; 553 | } else { 554 | nY[i] = (Y[(i - 1) / 2] + Y[(i - 1) / 2 + 1]) / 2; 555 | } 556 | for (j = 0; j < nxNum; j++) { 557 | if (i % 2 == 0 && j % 2 == 0) { 558 | nGridData[i][j] = GridData[i / 2][j / 2]; 559 | } else if (i % 2 == 0 && j % 2 != 0) { 560 | a = GridData[i / 2][(j - 1) / 2]; 561 | b = GridData[i / 2][(j - 1) / 2 + 1]; 562 | dList = new ArrayList<>(); 563 | if (a != unDefData) { 564 | dList.add(a); 565 | } 566 | if (b != unDefData) { 567 | dList.add(b); 568 | } 569 | 570 | if (dList.isEmpty()) { 571 | nGridData[i][j] = unDefData; 572 | } else if (dList.size() == 1) { 573 | nGridData[i][j] = dList.get(0); 574 | } else { 575 | nGridData[i][j] = (a + b) / 2; 576 | } 577 | } else if (i % 2 != 0 && j % 2 == 0) { 578 | a = GridData[(i - 1) / 2][j / 2]; 579 | b = GridData[(i - 1) / 2 + 1][j / 2]; 580 | dList = new ArrayList<>(); 581 | if (a != unDefData) { 582 | dList.add(a); 583 | } 584 | if (b != unDefData) { 585 | dList.add(b); 586 | } 587 | 588 | if (dList.isEmpty()) { 589 | nGridData[i][j] = unDefData; 590 | } else if (dList.size() == 1) { 591 | nGridData[i][j] = dList.get(0); 592 | } else { 593 | nGridData[i][j] = (a + b) / 2; 594 | } 595 | } else { 596 | a = GridData[(i - 1) / 2][(j - 1) / 2]; 597 | b = GridData[(i - 1) / 2][(j - 1) / 2 + 1]; 598 | c = GridData[(i - 1) / 2 + 1][(j - 1) / 2 + 1]; 599 | d = GridData[(i - 1) / 2 + 1][(j - 1) / 2]; 600 | dList = new ArrayList<>(); 601 | if (a != unDefData) { 602 | dList.add(a); 603 | } 604 | if (b != unDefData) { 605 | dList.add(b); 606 | } 607 | if (c != unDefData) { 608 | dList.add(c); 609 | } 610 | if (d != unDefData) { 611 | dList.add(d); 612 | } 613 | 614 | if (dList.isEmpty()) { 615 | nGridData[i][j] = unDefData; 616 | } else if (dList.size() == 1) { 617 | nGridData[i][j] = dList.get(0); 618 | } else { 619 | double aSum = 0; 620 | for (double dd : dList) { 621 | aSum += dd; 622 | } 623 | nGridData[i][j] = aSum / dList.size(); 624 | } 625 | } 626 | } 627 | } 628 | 629 | return nGridData; 630 | } 631 | 632 | // 633 | // 634 | /** 635 | * Assign point value to grid value 636 | * 637 | * @param SCoords point value array 638 | * @param X x coordinate 639 | * @param Y y coordinate 640 | * @param unDefData undefine value 641 | * @return grid data 642 | */ 643 | public static double[][] assignPointToGrid(double[][] SCoords, double[] X, double[] Y, 644 | double unDefData) { 645 | int rowNum, colNum, pNum; 646 | colNum = X.length; 647 | rowNum = Y.length; 648 | pNum = SCoords.length; 649 | double[][] GCoords = new double[rowNum][colNum]; 650 | double dX = X[1] - X[0]; 651 | double dY = Y[1] - Y[0]; 652 | int[][] pNums = new int[rowNum][colNum]; 653 | 654 | for (int i = 0; i < rowNum; i++) { 655 | for (int j = 0; j < colNum; j++) { 656 | pNums[i][j] = 0; 657 | GCoords[i][j] = 0.0; 658 | } 659 | } 660 | 661 | for (int p = 0; p < pNum; p++) { 662 | if (doubleEquals(SCoords[p][2], unDefData)) { 663 | continue; 664 | } 665 | 666 | double x = SCoords[p][0]; 667 | double y = SCoords[p][1]; 668 | if (x < X[0] || x > X[colNum - 1]) { 669 | continue; 670 | } 671 | if (y < Y[0] || y > Y[rowNum - 1]) { 672 | continue; 673 | } 674 | 675 | int j = (int) ((x - X[0]) / dX); 676 | int i = (int) ((y - Y[0]) / dY); 677 | pNums[i][j] += 1; 678 | GCoords[i][j] += SCoords[p][2]; 679 | } 680 | 681 | for (int i = 0; i < rowNum; i++) { 682 | for (int j = 0; j < colNum; j++) { 683 | if (pNums[i][j] == 0) { 684 | GCoords[i][j] = unDefData; 685 | } else { 686 | GCoords[i][j] = GCoords[i][j] / pNums[i][j]; 687 | } 688 | } 689 | } 690 | 691 | return GCoords; 692 | } 693 | 694 | private static boolean doubleEquals(double a, double b) { 695 | //if (Math.Abs(a - b) < 0.000001) 696 | if (Math.abs(a / b - 1) < 0.00000000001) { 697 | return true; 698 | } else { 699 | return false; 700 | } 701 | } 702 | // 703 | } 704 | -------------------------------------------------------------------------------- /src/main/java/contour/algorithm/KDTree.java: -------------------------------------------------------------------------------- 1 | package contour.algorithm; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | 6 | public abstract class KDTree { 7 | 8 | //use a big bucketSize so that we have less node bounds (for more cache hits) and better splits 9 | private static final int _bucketSize = 50; 10 | 11 | private final int _dimensions; 12 | private int _nodes; 13 | private final Node root; 14 | private final ArrayList nodeList = new ArrayList<>(); 15 | 16 | //prevent GC from having to collect _bucketSize*dimensions*8 bytes each time a leaf splits 17 | private double[] mem_recycle; 18 | 19 | //the starting values for bounding boxes, for easy access 20 | private final double[] bounds_template; 21 | 22 | //one big self-expanding array to keep all the node bounding boxes so that they stay in cache 23 | // node bounds available at: 24 | //low: 2 * _dimensions * node.index + 2 * dim 25 | //high: 2 * _dimensions * node.index + 2 * dim + 1 26 | private final ContiguousDoubleArrayList nodeMinMaxBounds; 27 | 28 | private KDTree(int dimensions) { 29 | _dimensions = dimensions; 30 | 31 | //initialise this big so that it ends up in 'old' memory 32 | nodeMinMaxBounds = new ContiguousDoubleArrayList(512 * 1024 / 8 + 2 * _dimensions); 33 | mem_recycle = new double[_bucketSize * dimensions]; 34 | 35 | bounds_template = new double[2 * _dimensions]; 36 | Arrays.fill(bounds_template, Double.NEGATIVE_INFINITY); 37 | for (int i = 0, max = 2 * _dimensions; i < max; i += 2) { 38 | bounds_template[i] = Double.POSITIVE_INFINITY; 39 | } 40 | 41 | //and.... start! 42 | root = new Node(); 43 | } 44 | 45 | public int nodes() { 46 | return _nodes; 47 | } 48 | 49 | public int size() { 50 | return root.entries; 51 | } 52 | 53 | public int addPoint(double[] location, T payload) { 54 | 55 | Node addNode = root; 56 | //Do a Depth First Search to find the Node where 'location' should be stored 57 | while (addNode.pointLocations == null) { 58 | addNode.expandBounds(location); 59 | if (location[addNode.splitDim] < addNode.splitVal) { 60 | addNode = nodeList.get(addNode.lessIndex); 61 | } else { 62 | addNode = nodeList.get(addNode.moreIndex); 63 | } 64 | } 65 | addNode.expandBounds(location); 66 | 67 | int nodeSize = addNode.add(location, payload); 68 | 69 | if (nodeSize % _bucketSize == 0) //try splitting again once every time the node passes a _bucketSize multiple 70 | //in case it is full of points of the same location and won't split 71 | { 72 | addNode.split(); 73 | } 74 | 75 | return root.entries; 76 | } 77 | 78 | public ArrayList> nearestNeighbours(double[] searchLocation, int K) { 79 | IntStack stack = new IntStack(); 80 | PrioQueue results = new PrioQueue<>(K, true); 81 | 82 | stack.push(root.index); 83 | 84 | int added = 0; 85 | 86 | while (stack.size() > 0) { 87 | int nodeIndex = stack.pop(); 88 | if (added < K || results.peekPrio() > pointRectDist(nodeIndex, searchLocation)) { 89 | Node node = nodeList.get(nodeIndex); 90 | if (node.pointLocations == null) { 91 | node.search(searchLocation, stack); 92 | } else { 93 | added += node.search(searchLocation, results); 94 | } 95 | } 96 | } 97 | 98 | ArrayList> returnResults = new ArrayList<>(K); 99 | double[] priorities = results.priorities; 100 | Object[] elements = results.elements; 101 | for (int i = 0; i < K; i++) {//forward (closest first) 102 | SearchResult s = new SearchResult(priorities[i], (T) elements[i]); 103 | returnResults.add(s); 104 | } 105 | return returnResults; 106 | } 107 | 108 | public ArrayList ballSearch(double[] searchLocation, double radius) { 109 | IntStack stack = new IntStack(); 110 | ArrayList results = new ArrayList<>(); 111 | 112 | stack.push(root.index); 113 | 114 | while (stack.size() > 0) { 115 | int nodeIndex = stack.pop(); 116 | if (radius > pointRectDist(nodeIndex, searchLocation)) { 117 | Node node = nodeList.get(nodeIndex); 118 | if (node.pointLocations == null) { 119 | stack.push(node.moreIndex).push(node.lessIndex); 120 | } else { 121 | node.searchBall(searchLocation, radius, results); 122 | } 123 | } 124 | } 125 | return results; 126 | } 127 | 128 | public ArrayList rectSearch(double[] mins, double[] maxs) { 129 | IntStack stack = new IntStack(); 130 | ArrayList results = new ArrayList<>(); 131 | 132 | stack.push(root.index); 133 | 134 | while (stack.size() > 0) { 135 | int nodeIndex = stack.pop(); 136 | if (overlaps(mins, maxs, nodeIndex)) { 137 | Node node = nodeList.get(nodeIndex); 138 | if (node.pointLocations == null) { 139 | stack.push(node.moreIndex).push(node.lessIndex); 140 | } else { 141 | node.searchRect(mins, maxs, results); 142 | } 143 | } 144 | } 145 | return results; 146 | 147 | } 148 | 149 | abstract double pointRectDist(int offset, final double[] location); 150 | 151 | abstract double pointDist(double[] arr, double[] location, int index); 152 | 153 | boolean contains(double[] arr, double[] mins, double[] maxs, int index) { 154 | 155 | int offset = (index + 1) * mins.length; 156 | 157 | for (int i = mins.length; i-- > 0;) { 158 | double d = arr[--offset]; 159 | if (mins[i] > d | d > maxs[i]) { 160 | return false; 161 | } 162 | } 163 | return true; 164 | } 165 | 166 | boolean overlaps(double[] mins, double[] maxs, int offset) { 167 | offset *= (2 * maxs.length); 168 | final double[] array = nodeMinMaxBounds.array; 169 | for (int i = 0; i < maxs.length; i++, offset += 2) { 170 | double bmin = array[offset], bmax = array[offset + 1]; 171 | if (mins[i] > bmax | maxs[i] < bmin) { 172 | return false; 173 | } 174 | } 175 | 176 | return true; 177 | } 178 | 179 | public static class Euclidean extends KDTree { 180 | 181 | public Euclidean(int dims) { 182 | super(dims); 183 | } 184 | 185 | @Override 186 | double pointRectDist(int offset, final double[] location) { 187 | offset *= (2 * super._dimensions); 188 | double distance = 0; 189 | final double[] array = super.nodeMinMaxBounds.array; 190 | for (int i = 0; i < location.length; i++, offset += 2) { 191 | 192 | double diff = 0; 193 | double bv = array[offset]; 194 | double lv = location[i]; 195 | if (bv > lv) { 196 | diff = bv - lv; 197 | } else { 198 | bv = array[offset + 1]; 199 | if (lv > bv) { 200 | diff = lv - bv; 201 | } 202 | } 203 | distance += sqr(diff); 204 | } 205 | return distance; 206 | } 207 | 208 | @Override 209 | double pointDist(double[] arr, double[] location, int index) { 210 | double distance = 0; 211 | int offset = (index + 1) * super._dimensions; 212 | 213 | for (int i = super._dimensions; i-- > 0;) { 214 | distance += sqr(arr[--offset] - location[i]); 215 | } 216 | return distance; 217 | } 218 | 219 | } 220 | 221 | public static class Manhattan extends KDTree { 222 | 223 | public Manhattan(int dims) { 224 | super(dims); 225 | } 226 | 227 | @Override 228 | double pointRectDist(int offset, final double[] location) { 229 | offset *= (2 * super._dimensions); 230 | double distance = 0; 231 | final double[] array = super.nodeMinMaxBounds.array; 232 | for (int i = 0; i < location.length; i++, offset += 2) { 233 | 234 | double diff = 0; 235 | double bv = array[offset]; 236 | double lv = location[i]; 237 | if (bv > lv) { 238 | diff = bv - lv; 239 | } else { 240 | bv = array[offset + 1]; 241 | if (lv > bv) { 242 | diff = lv - bv; 243 | } 244 | } 245 | distance += (diff); 246 | } 247 | return distance; 248 | } 249 | 250 | @Override 251 | double pointDist(double[] arr, double[] location, int index) { 252 | double distance = 0; 253 | int offset = (index + 1) * super._dimensions; 254 | 255 | for (int i = super._dimensions; i-- > 0;) { 256 | distance += Math.abs(arr[--offset] - location[i]); 257 | } 258 | return distance; 259 | } 260 | } 261 | 262 | public static class WeightedManhattan extends KDTree { 263 | 264 | double[] weights; 265 | 266 | public WeightedManhattan(int dims) { 267 | super(dims); 268 | } 269 | 270 | public void setWeights(double[] newWeights) { 271 | weights = newWeights; 272 | } 273 | 274 | @Override 275 | double pointRectDist(int offset, final double[] location) { 276 | offset *= (2 * super._dimensions); 277 | double distance = 0; 278 | final double[] array = super.nodeMinMaxBounds.array; 279 | for (int i = 0; i < location.length; i++, offset += 2) { 280 | 281 | double diff = 0; 282 | double bv = array[offset]; 283 | double lv = location[i]; 284 | if (bv > lv) { 285 | diff = bv - lv; 286 | } else { 287 | bv = array[offset + 1]; 288 | if (lv > bv) { 289 | diff = lv - bv; 290 | } 291 | } 292 | distance += (diff) * weights[i]; 293 | } 294 | return distance; 295 | } 296 | 297 | @Override 298 | double pointDist(double[] arr, double[] location, int index) { 299 | double distance = 0; 300 | int offset = (index + 1) * super._dimensions; 301 | 302 | for (int i = super._dimensions; i-- > 0;) { 303 | distance += Math.abs(arr[--offset] - location[i]) * weights[i]; 304 | } 305 | return distance; 306 | } 307 | } 308 | 309 | //NB! This Priority Queue keeps things with the LOWEST priority. 310 | //If you want highest priority items kept, negate your values 311 | private static class PrioQueue { 312 | 313 | Object[] elements; 314 | double[] priorities; 315 | private double minPrio; 316 | private int size; 317 | 318 | PrioQueue(int size, boolean prefill) { 319 | elements = new Object[size]; 320 | priorities = new double[size]; 321 | Arrays.fill(priorities, Double.POSITIVE_INFINITY); 322 | if (prefill) { 323 | minPrio = Double.POSITIVE_INFINITY; 324 | this.size = size; 325 | } 326 | } 327 | //uses O(log(n)) comparisons and one big shift of size O(N) 328 | //and is MUCH simpler than a heap --> faster on small sets, faster JIT 329 | 330 | void addNoGrow(S value, double priority) { 331 | int index = searchFor(priority); 332 | int nextIndex = index + 1; 333 | int length = size - index - 1; 334 | System.arraycopy(elements, index, elements, nextIndex, length); 335 | System.arraycopy(priorities, index, priorities, nextIndex, length); 336 | elements[index] = value; 337 | priorities[index] = priority; 338 | 339 | minPrio = priorities[size - 1]; 340 | } 341 | 342 | int searchFor(double priority) { 343 | int i = size - 1; 344 | int j = 0; 345 | while (i >= j) { 346 | int index = (i + j) >>> 1; 347 | if (priorities[index] < priority) { 348 | j = index + 1; 349 | } else { 350 | i = index - 1; 351 | } 352 | } 353 | return j; 354 | } 355 | 356 | double peekPrio() { 357 | return minPrio; 358 | } 359 | } 360 | 361 | public static class SearchResult { 362 | 363 | public double distance; 364 | public S payload; 365 | 366 | SearchResult(double dist, S load) { 367 | distance = dist; 368 | payload = load; 369 | } 370 | } 371 | 372 | private class Node { 373 | 374 | //for accessing bounding box data 375 | // - if trees weren't so unbalanced might be better to use an implicit heap? 376 | int index; 377 | 378 | //keep track of size of subtree 379 | int entries; 380 | 381 | //leaf 382 | ContiguousDoubleArrayList pointLocations; 383 | ArrayList pointPayloads = new ArrayList<>(_bucketSize); 384 | 385 | //stem 386 | //Node less, more; 387 | int lessIndex, moreIndex; 388 | int splitDim; 389 | double splitVal; 390 | 391 | Node() { 392 | this(new double[_bucketSize * _dimensions]); 393 | } 394 | 395 | Node(double[] pointMemory) { 396 | pointLocations = new ContiguousDoubleArrayList(pointMemory); 397 | index = _nodes++; 398 | nodeList.add(this); 399 | nodeMinMaxBounds.add(bounds_template); 400 | } 401 | 402 | void search(double[] searchLocation, IntStack stack) { 403 | if (searchLocation[splitDim] < splitVal) { 404 | stack.push(moreIndex).push(lessIndex);//less will be popped first 405 | } else { 406 | stack.push(lessIndex).push(moreIndex);//more will be popped first 407 | } 408 | } 409 | 410 | //returns number of points added to results 411 | int search(double[] searchLocation, PrioQueue results) { 412 | int updated = 0; 413 | for (int j = entries; j-- > 0;) { 414 | double distance = pointDist(pointLocations.array, searchLocation, j); 415 | if (results.peekPrio() > distance) { 416 | updated++; 417 | results.addNoGrow(pointPayloads.get(j), distance); 418 | } 419 | } 420 | return updated; 421 | } 422 | 423 | void searchBall(double[] searchLocation, double radius, ArrayList results) { 424 | 425 | for (int j = entries; j-- > 0;) { 426 | double distance = pointDist(pointLocations.array, searchLocation, j); 427 | if (radius >= distance) { 428 | results.add(pointPayloads.get(j)); 429 | } 430 | } 431 | } 432 | 433 | void searchRect(double[] mins, double[] maxs, ArrayList results) { 434 | 435 | for (int j = entries; j-- > 0;) { 436 | if (contains(pointLocations.array, mins, maxs, j)) { 437 | results.add(pointPayloads.get(j)); 438 | } 439 | } 440 | 441 | } 442 | 443 | void expandBounds(double[] location) { 444 | entries++; 445 | int mio = index * 2 * _dimensions; 446 | for (int i = 0; i < _dimensions; i++) { 447 | nodeMinMaxBounds.array[mio] = Math.min(nodeMinMaxBounds.array[mio++], location[i]); 448 | nodeMinMaxBounds.array[mio] = Math.max(nodeMinMaxBounds.array[mio++], location[i]); 449 | } 450 | } 451 | 452 | int add(double[] location, T load) { 453 | pointLocations.add(location); 454 | pointPayloads.add(load); 455 | return entries; 456 | } 457 | 458 | void split() { 459 | int offset = index * 2 * _dimensions; 460 | 461 | double diff = 0; 462 | for (int i = 0; i < _dimensions; i++) { 463 | double min = nodeMinMaxBounds.array[offset]; 464 | double max = nodeMinMaxBounds.array[offset + 1]; 465 | if (max - min > diff) { 466 | double mean = 0; 467 | for (int j = 0; j < entries; j++) { 468 | mean += pointLocations.array[i + _dimensions * j]; 469 | } 470 | 471 | mean = mean / entries; 472 | double varianceSum = 0; 473 | 474 | for (int j = 0; j < entries; j++) { 475 | varianceSum += sqr(mean - pointLocations.array[i + _dimensions * j]); 476 | } 477 | 478 | if (varianceSum > diff * entries) { 479 | diff = varianceSum / entries; 480 | splitVal = mean; 481 | 482 | splitDim = i; 483 | } 484 | } 485 | offset += 2; 486 | } 487 | 488 | //kill all the nasties 489 | if (splitVal == Double.POSITIVE_INFINITY) { 490 | splitVal = Double.MAX_VALUE; 491 | } else if (splitVal == Double.NEGATIVE_INFINITY) { 492 | splitVal = Double.MIN_VALUE; 493 | } else if (splitVal == nodeMinMaxBounds.array[index * 2 * _dimensions + 2 * splitDim + 1]) { 494 | splitVal = nodeMinMaxBounds.array[index * 2 * _dimensions + 2 * splitDim]; 495 | } 496 | 497 | Node less = new Node(mem_recycle);//recycle that memory! 498 | Node more = new Node(); 499 | lessIndex = less.index; 500 | moreIndex = more.index; 501 | 502 | //reduce garbage by factor of _bucketSize by recycling this array 503 | double[] pointLocation = new double[_dimensions]; 504 | for (int i = 0; i < entries; i++) { 505 | System.arraycopy(pointLocations.array, i * _dimensions, pointLocation, 0, _dimensions); 506 | T load = pointPayloads.get(i); 507 | 508 | if (pointLocation[splitDim] < splitVal) { 509 | less.expandBounds(pointLocation); 510 | less.add(pointLocation, load); 511 | } else { 512 | more.expandBounds(pointLocation); 513 | more.add(pointLocation, load); 514 | } 515 | } 516 | if (less.entries * more.entries == 0) { 517 | //one of them was 0, so the split was worthless. throw it away. 518 | _nodes -= 2;//recall that bounds memory 519 | nodeList.remove(moreIndex); 520 | nodeList.remove(lessIndex); 521 | } else { 522 | 523 | //we won't be needing that now, so keep it for the next split to reduce garbage 524 | mem_recycle = pointLocations.array; 525 | 526 | pointLocations = null; 527 | 528 | pointPayloads.clear(); 529 | pointPayloads = null; 530 | } 531 | } 532 | 533 | } 534 | 535 | private static class ContiguousDoubleArrayList { 536 | 537 | double[] array; 538 | int size; 539 | 540 | ContiguousDoubleArrayList() { 541 | this(300); 542 | } 543 | 544 | ContiguousDoubleArrayList(int size) { 545 | this(new double[size]); 546 | } 547 | 548 | ContiguousDoubleArrayList(double[] data) { 549 | array = data; 550 | } 551 | 552 | ContiguousDoubleArrayList add(double[] da) { 553 | if (size + da.length > array.length) { 554 | array = Arrays.copyOf(array, (array.length + da.length) * 2); 555 | } 556 | 557 | System.arraycopy(da, 0, array, size, da.length); 558 | size += da.length; 559 | return this; 560 | } 561 | } 562 | 563 | private static class IntStack { 564 | 565 | int[] array; 566 | int size; 567 | 568 | IntStack() { 569 | this(64); 570 | } 571 | 572 | IntStack(int size) { 573 | this(new int[size]); 574 | } 575 | 576 | IntStack(int[] data) { 577 | array = data; 578 | } 579 | 580 | IntStack push(int i) { 581 | if (size >= array.length) { 582 | array = Arrays.copyOf(array, (array.length + 1) * 2); 583 | } 584 | 585 | array[size++] = i; 586 | return this; 587 | } 588 | 589 | int pop() { 590 | return array[--size]; 591 | } 592 | 593 | int size() { 594 | return size; 595 | } 596 | } 597 | 598 | static final double sqr(double d) { 599 | return d * d; 600 | } 601 | } 602 | -------------------------------------------------------------------------------- /src/main/java/contour/algorithm/Kriging.java: -------------------------------------------------------------------------------- 1 | package contour.algorithm; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | 8 | import contour.utils.MathsUtil; 9 | 10 | /** 11 | * Kriging 12 | * 13 | * @author xuwei 14 | */ 15 | 16 | public class Kriging { 17 | /** 18 | * 高斯模型 19 | */ 20 | public final static String GAUSSIAN_MODEL = "gaussian"; 21 | /** 22 | * 指数模型 23 | */ 24 | public final static String EXPONENTIAL_MODEL = "exponential"; 25 | /** 26 | * 球体模型 27 | */ 28 | public final static String SPHERICAL_MODEL = "Spherical"; 29 | 30 | public String model = EXPONENTIAL_MODEL; 31 | /** 32 | * 方差 33 | */ 34 | public double sigma2 = 0; 35 | /** 36 | * 37 | */ 38 | public double alpha = 100; 39 | 40 | private double[] targetValues; 41 | 42 | private double[] xList; 43 | 44 | private double[] yList; 45 | 46 | private double variogram_nugget = 0; 47 | 48 | private double variogram_range = 0; 49 | 50 | private double variogram_sill = 0; 51 | 52 | private double variogram_A = (double) 1 / 3; 53 | 54 | private int variogram_n = 0; 55 | 56 | private double[] variogram_K; 57 | 58 | private double[] variogram_M; 59 | 60 | public Kriging(double nugget, double range, double sill, double A, int n) { 61 | this.variogram_nugget = nugget; 62 | this.variogram_range = range; 63 | this.variogram_sill = sill; 64 | this.variogram_A = A; 65 | this.variogram_n = n; 66 | } 67 | 68 | public Kriging(String model, double sigma2, double alpha) { 69 | this.model = model; 70 | this.sigma2 = sigma2; 71 | this.alpha = alpha; 72 | } 73 | 74 | private double variogram_gaussian(double h) { 75 | double nugget = this.variogram_nugget; 76 | double sill = this.variogram_sill; 77 | double range = this.variogram_range; 78 | double A = this.variogram_A; 79 | 80 | return nugget + ((sill - nugget) / range) * (1.0 - Math.exp(-(1.0 / A) * Math.pow(h / range, 2))); 81 | } 82 | 83 | private double variogram_exponential(double h) { 84 | double nugget = this.variogram_nugget; 85 | double sill = this.variogram_sill; 86 | double range = this.variogram_range; 87 | double A = this.variogram_A; 88 | 89 | return nugget + ((sill - nugget) / range) * (1.0 - Math.exp(-(1.0 / A) * (h / range))); 90 | } 91 | 92 | private double variogram_spherical(double h) { 93 | double nugget = this.variogram_nugget; 94 | double sill = this.variogram_sill; 95 | double range = this.variogram_range; 96 | 97 | if (h > range) 98 | return nugget + (sill - nugget) / range; 99 | 100 | return nugget + ((sill - nugget) / range) * (1.5 * (h / range) - 0.5 * Math.pow(h / range, 3)); 101 | } 102 | 103 | /** 104 | * training sample data 105 | * 106 | */ 107 | public void train(double[] targetValues, double[] xList, double[] yList) { 108 | this.targetValues = targetValues; 109 | this.xList = xList; 110 | this.yList = yList; 111 | 112 | // lag distance/semivariance 113 | int size = this.targetValues.length; 114 | int distance_capacity = size * (size - 1) / 2; 115 | List distances = new ArrayList(distance_capacity); 116 | int i, j, k, l; 117 | for (i = 0, k = 0; i < size; i++) { 118 | for (j = 0; j < i; j++, k++) { 119 | double distance = Math.sqrt(Math.pow(xList[i] - xList[j], 2) + Math.pow(yList[i] - yList[j], 2)); 120 | double detaValue = Math.abs(targetValues[i] - targetValues[j]); 121 | double[] arr = { distance, detaValue }; 122 | distances.add(arr); 123 | } 124 | } 125 | Collections.sort(distances, new Comparator() { 126 | @Override 127 | public int compare(double[] o1, double[] o2) { 128 | double delta = o1[0] - o2[0]; 129 | if (delta < 0) { 130 | return -1; 131 | } else if (delta > 0) { 132 | return 1; 133 | } 134 | return 0; 135 | } 136 | }); 137 | this.variogram_range = distances.get(distance_capacity - 1)[0]; 138 | 139 | // Bin lag distance 140 | int lags = distance_capacity > 30 ? 30 : distance_capacity; 141 | double tolerance = (double) this.variogram_range / lags; 142 | double[] lag = new double[lags]; 143 | double[] semi = new double[lags]; 144 | if (lags < 30) { 145 | for (l = 0; l < lags; l++) { 146 | double[] distance = distances.get(l); 147 | lag[l] = distance[0]; 148 | semi[l] = distance[1]; 149 | } 150 | } else { 151 | for (i = 0, j = 0, k = 0, l = 0; i < lags && j < distance_capacity; i++, k = 0) { 152 | while (distances.get(j)[0] <= ((i + 1) * tolerance)) { 153 | lag[l] += distances.get(j)[0]; 154 | semi[l] += distances.get(j)[1]; 155 | j++; 156 | k++; 157 | if (j >= distance_capacity) 158 | break; 159 | } 160 | if (k > 0) { 161 | lag[l] /= k; 162 | semi[l] /= k; 163 | l++; 164 | } 165 | } 166 | if (l < 2) { 167 | try { 168 | throw new Exception("Not enough points."); 169 | } catch (Exception e) { 170 | e.printStackTrace(); 171 | } 172 | }; 173 | } 174 | 175 | // Feature transformation 176 | size = l; 177 | this.variogram_range = lag[size - 1] - lag[0]; 178 | double[] X = new double[size*2]; 179 | for(int n=0;n xlim[1]) 303 | xlim[1] = polygons[i][j][0]; 304 | if (polygons[i][j][1] < ylim[0]) 305 | ylim[0] = polygons[i][j][1]; 306 | if (polygons[i][j][1] > ylim[1]) 307 | ylim[1] = polygons[i][j][1]; 308 | } 309 | 310 | // Alloc for O(n^2) space 311 | double xtarget, ytarget; 312 | int[] a = new int[2]; 313 | int[] b = new int[2]; 314 | double[] lxlim = new double[2]; // Local dimensions 315 | double[] lylim = new double[2]; // Local dimensions 316 | int x = (int)Math.ceil((xlim[1] - xlim[0]) / xWidth); 317 | int y = (int)Math.ceil((ylim[1] - ylim[0]) / yWidth); 318 | 319 | double[][] A = new double[x + 1][]; 320 | for (i = 0; i <= x; i++) A[i] = new double[y + 1]; 321 | for (i = 0; i < n; i++) { 322 | // Range for polygons[i] 323 | lxlim[0] = polygons[i][0][0]; 324 | lxlim[1] = lxlim[0]; 325 | lylim[0] = polygons[i][0][1]; 326 | lylim[1] = lylim[0]; 327 | for (j = 1; j < polygons[i].length; j++) { // Vertices 328 | if (polygons[i][j][0] < lxlim[0]) 329 | lxlim[0] = polygons[i][j][0]; 330 | if (polygons[i][j][0] > lxlim[1]) 331 | lxlim[1] = polygons[i][j][0]; 332 | if (polygons[i][j][1] < lylim[0]) 333 | lylim[0] = polygons[i][j][1]; 334 | if (polygons[i][j][1] > lylim[1]) 335 | lylim[1] = polygons[i][j][1]; 336 | } 337 | 338 | // Loop through polygon subspace 339 | a[0] = (int)Math.floor(((lxlim[0] - ((lxlim[0] - xlim[0]) % xWidth)) - xlim[0]) / xWidth); 340 | a[1] = (int)Math.ceil(((lxlim[1] - ((lxlim[1] - xlim[1]) % xWidth)) - xlim[0]) / xWidth); 341 | b[0] = (int)Math.floor(((lylim[0] - ((lylim[0] - ylim[0]) % yWidth)) - ylim[0]) / yWidth); 342 | b[1] = (int)Math.ceil(((lylim[1] - ((lylim[1] - ylim[1]) % yWidth)) - ylim[0]) / yWidth); 343 | for (j = a[0]; j <= a[1]; j++) 344 | for (k = b[0]; k <= b[1]; k++) { 345 | xtarget = xlim[0] + j * xWidth; 346 | ytarget = ylim[0] + k * yWidth; 347 | if (pip(polygons[i], xtarget, ytarget)) { 348 | A[j][k] = predict(xtarget, ytarget); 349 | } 350 | } 351 | } 352 | 353 | double maxValue = targetValues[0], minValue = targetValues[0]; 354 | int len = targetValues.length; 355 | for(i=0; i < len; i++){ 356 | if(targetValues[i] > maxValue) maxValue = targetValues[i]; 357 | if(targetValues[i] < minValue) minValue = targetValues[i]; 358 | 359 | } 360 | 361 | Grid grid = new Grid(); 362 | grid.A = A; 363 | grid.xlim = xlim; 364 | grid.ylim = ylim; 365 | grid.zlim = new double[]{minValue, maxValue}; 366 | grid.xWidth = xWidth; 367 | grid.yWidth = yWidth; 368 | 369 | return grid; 370 | } 371 | 372 | public double variance_gassian(double x, double y) { 373 | double[] k = new double[variogram_n]; 374 | for (int i = 0; i < variogram_n; i++){ 375 | k[i] = variogram_gaussian(Math.sqrt(Math.pow(x - this.xList[i], 2) + 376 | Math.pow(y - this.yList[i], 2))); 377 | } 378 | 379 | return variogram_gaussian(0) + 380 | MathsUtil.kriging_matrix_multiply(MathsUtil.kriging_matrix_multiply(k, variogram_K, 381 | 1, variogram_n, variogram_n), 382 | k, 1, variogram_n, 1)[0]; 383 | } 384 | 385 | public double variance_exponential(double x, double y) { 386 | double[] k = new double[variogram_n]; 387 | for (int i = 0; i < variogram_n; i++){ 388 | k[i] = variogram_exponential(Math.sqrt(Math.pow(x - this.xList[i], 2) + 389 | Math.pow(y - this.yList[i], 2))); 390 | } 391 | 392 | return variogram_exponential(0) + 393 | MathsUtil.kriging_matrix_multiply(MathsUtil.kriging_matrix_multiply(k, variogram_K, 394 | 1, variogram_n, variogram_n), 395 | k, 1, variogram_n, 1)[0]; 396 | } 397 | 398 | public double variance_spherical(double x, double y) { 399 | double[] k = new double[variogram_n]; 400 | for (int i = 0; i < variogram_n; i++){ 401 | k[i] = variogram_spherical(Math.sqrt(Math.pow(x - this.xList[i], 2) + 402 | Math.pow(y - this.yList[i], 2))); 403 | } 404 | 405 | return variogram_spherical(0) + 406 | MathsUtil.kriging_matrix_multiply(MathsUtil.kriging_matrix_multiply(k, variogram_K, 407 | 1, variogram_n, variogram_n), 408 | k, 1, variogram_n, 1)[0]; 409 | } 410 | 411 | private double[] build_gassian_gram_matrix_with_prior(double[] xList, double[] yList){ 412 | int n = xList.length; 413 | double[] K = new double[n * n]; 414 | for(int i = 0; i < n; i++){ 415 | for (int j = 0; j < i; j++) { 416 | K[i * n + j] = variogram_gaussian(Math.sqrt(Math.pow(xList[i] - xList[j], 2) + 417 | Math.pow(yList[i] - yList[j], 2))); 418 | K[j * n + i] = K[i * n + j]; 419 | } 420 | K[i * n + i] = variogram_gaussian(0); 421 | } 422 | return K; 423 | } 424 | 425 | private double[] build_exponential_gram_matrix_with_prior(double[] xList, double[] yList){ 426 | int n = xList.length; 427 | double[] K = new double[n * n]; 428 | for(int i = 0; i < n; i++){ 429 | for (int j = 0; j < i; j++) { 430 | K[i * n + j] = variogram_exponential(Math.sqrt(Math.pow(xList[i] - xList[j], 2) + 431 | Math.pow(yList[i] - yList[j], 2))); 432 | K[j * n + i] = K[i * n + j]; 433 | } 434 | K[i * n + i] = variogram_exponential(0); 435 | } 436 | return K; 437 | } 438 | 439 | private double[] build_spherical_gram_matrix_with_prior(double[] xList, double[] yList){ 440 | int n = xList.length; 441 | double[] K = new double[n * n]; 442 | for(int i = 0; i < n; i++){ 443 | for (int j = 0; j < i; j++) { 444 | K[i * n + j] = variogram_spherical(Math.sqrt(Math.pow(xList[i] - xList[j], 2) + 445 | Math.pow(yList[i] - yList[j], 2))); 446 | K[j * n + i] = K[i * n + j]; 447 | } 448 | K[i * n + i] = variogram_spherical(0); 449 | } 450 | return K; 451 | } 452 | 453 | private boolean pip(double[][] polygons, double x, double y) { 454 | int i, j; 455 | boolean c = false; 456 | for (i = 0, j = polygons.length - 1; i < polygons.length; j = i++) { 457 | if (((polygons[i][1] > y) != (polygons[j][1] > y)) && 458 | (x < (polygons[j][0] - polygons[i][0]) * (y - polygons[i][1]) / (polygons[j][1] - polygons[i][1]) + polygons[i][0])) { 459 | c = !c; 460 | } 461 | } 462 | return c; 463 | } 464 | 465 | } -------------------------------------------------------------------------------- /src/main/java/contour/bean/DrawStyle.java: -------------------------------------------------------------------------------- 1 | package contour.bean; 2 | 3 | import java.awt.*; 4 | 5 | public class DrawStyle { 6 | 7 | public boolean show; 8 | public int size; 9 | public Color color; 10 | 11 | public DrawStyle(boolean show, int size, Color color) { 12 | this.show = show; 13 | this.size = size; 14 | this.color = color; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/contour/bean/Tuple2.java: -------------------------------------------------------------------------------- 1 | package contour.bean; 2 | 3 | public class Tuple2 { 4 | 5 | public final A _1; 6 | public final B _2; 7 | 8 | public Tuple2(A a, B b) { 9 | this._1 = a; 10 | this._2 = b; 11 | } 12 | 13 | @Override 14 | public String toString() { 15 | return "(" + 16 | _1 + 17 | ","+ _2 + 18 | ')'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/contour/bean/Tuple3.java: -------------------------------------------------------------------------------- 1 | package contour.bean; 2 | 3 | public class Tuple3 { 4 | 5 | public final A _1; 6 | public final B _2; 7 | public final C _3; 8 | 9 | public Tuple3(A a, B b, C c) { 10 | this._1 = a; 11 | this._2 = b; 12 | this._3 = c; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "(" + 18 | _1 + 19 | ","+ _2 + 20 | ","+ _3 + 21 | ')'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/contour/bean/Tuple5.java: -------------------------------------------------------------------------------- 1 | package contour.bean; 2 | 3 | /** 4 | * 颜色元组 5 | * Tuple5 6 | */ 7 | public class Tuple5 { 8 | public final A _1; 9 | public final B _2; 10 | public final C _3; 11 | public final D _4; 12 | public final E _5; 13 | 14 | public Tuple5(A _1, B _2, C _3, D _4, E _5) { 15 | this._1 = _1; 16 | this._2 = _2; 17 | this._3 = _3; 18 | this._4 = _4; 19 | this._5 = _5; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return "(" + 25 | _1 + 26 | "," + _2 + 27 | "," + _3 + 28 | "," + _4 + 29 | "," + _5 + 30 | ')'; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/Border.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | import java.util.List; 8 | import java.util.ArrayList; 9 | 10 | /** 11 | * Border class - contour line border 12 | * 13 | * @author Yaqiang Wang 14 | * @version $Revision: 1.6 $ 15 | */ 16 | public class Border { 17 | public List LineList = new ArrayList<>(); 18 | 19 | /** 20 | * Constructor 21 | */ 22 | public Border() 23 | { 24 | 25 | } 26 | 27 | /** 28 | * Get line number 29 | * @return Line number 30 | */ 31 | public int getLineNum(){ 32 | return LineList.size(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/BorderLine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * BorderLine class 12 | * 13 | * @author Yaqiang Wang 14 | */ 15 | public class BorderLine { 16 | 17 | public double area; 18 | public Extent extent = new Extent(); 19 | public boolean isOutLine; 20 | public boolean isClockwise; 21 | public List pointList = new ArrayList<>(); 22 | public List ijPointList = new ArrayList<>(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/BorderPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | /** 8 | * BorderPoint class 9 | * 10 | * @author Yaqiang Wang 11 | */ 12 | public class BorderPoint { 13 | 14 | public int Id; 15 | public int BorderIdx; 16 | public int BInnerIdx; 17 | public PointD Point = new PointD(); 18 | public double Value; 19 | 20 | @Override 21 | public Object clone() { 22 | BorderPoint aBP = new BorderPoint(); 23 | aBP.Id = Id; 24 | aBP.BorderIdx = BorderIdx; 25 | aBP.BInnerIdx = BInnerIdx; 26 | aBP.Point = Point; 27 | aBP.Value = Value; 28 | 29 | return aBP; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/EndPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | /** 8 | * EndPoint class 9 | * 10 | * @author Yaqiang Wang 11 | */ 12 | public class EndPoint { 13 | 14 | public PointD sPoint = new PointD(); 15 | public PointD Point = new PointD(); 16 | public int Index; 17 | public int BorderIdx; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/Extent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | /** 8 | * Extent class 9 | * 10 | * @author Yaqiang Wang 11 | */ 12 | public class Extent { 13 | 14 | public double xMin; 15 | public double yMin; 16 | public double xMax; 17 | public double yMax; 18 | 19 | /** 20 | * Constructor 21 | */ 22 | public Extent() { 23 | 24 | } 25 | 26 | /** 27 | * Constructor 28 | * 29 | * @param minX Minimum x 30 | * @param maxX Maximum x 31 | * @param minY Minimum y 32 | * @param maxY Maximum y 33 | */ 34 | public Extent(double minX, double maxX, double minY, double maxY) { 35 | xMin = minX; 36 | xMax = maxX; 37 | yMin = minY; 38 | yMax = maxY; 39 | } 40 | 41 | /** 42 | * Judge if this extent include another extent 43 | * 44 | * @param bExtent The extent 45 | * @return Is included or not 46 | */ 47 | public boolean Include(Extent bExtent) { 48 | return xMin <= bExtent.xMin && xMax >= bExtent.xMax && yMin <= bExtent.yMin && yMax >= bExtent.yMax; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/IJPoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | /** 8 | * Point integer, to indicate the position in grid data 9 | * 10 | * @author Yaqiang Wang 11 | */ 12 | public class IJPoint { 13 | 14 | public int I; // row in matrix 15 | public int J; // column in matrix 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/LPolygon.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 11 | * @author Yaqiang Wang 12 | */ 13 | public class LPolygon { 14 | 15 | public double value; 16 | public boolean isFirst; 17 | public List pointList; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/LegendPara.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | /** 8 | * 9 | * @author Yaqiang Wang 10 | */ 11 | public class LegendPara { 12 | 13 | public boolean isVertical; 14 | public PointD startPoint; 15 | public double length; 16 | public double width; 17 | public double[] contourValues; 18 | public boolean isTriangle; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/Line.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | /** 8 | * 9 | * Line class 10 | * 11 | * @author Yaqiang Wang 12 | */ 13 | public class Line { 14 | 15 | public PointD P1; 16 | public PointD P2; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/PointD.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | /** 8 | * PointD class 9 | * 10 | * @author Yaqiang Wang 11 | */ 12 | public class PointD { 13 | 14 | public double X; 15 | public double Y; 16 | 17 | /** 18 | * Constructor 19 | */ 20 | public PointD() { 21 | X = 0.0; 22 | Y = 0.0; 23 | } 24 | 25 | /** 26 | * Constructor 27 | * 28 | * @param x X 29 | * @param y Y 30 | */ 31 | public PointD(double x, double y) { 32 | X = x; 33 | Y = y; 34 | } 35 | 36 | @Override 37 | public Object clone() { 38 | return new PointD(X, Y); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/PointF.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | /** 8 | * PointF class 9 | * 10 | * @author Yaqiang Wang 11 | */ 12 | public class PointF { 13 | 14 | public float X; 15 | public float Y; 16 | 17 | /** 18 | * Constructor 19 | */ 20 | public PointF() { 21 | 22 | } 23 | 24 | /** 25 | * Constructor 26 | * 27 | * @param x X 28 | * @param y Y 29 | */ 30 | public PointF(float x, float y) { 31 | X = x; 32 | Y = y; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/PolyLine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * PolyLine class 12 | * 13 | * @author Yaqiang Wang 14 | */ 15 | public class PolyLine { 16 | 17 | public double Value; 18 | public String Type; 19 | public int BorderIdx; 20 | public List PointList = new ArrayList<>(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/contour/draw/spatial/Polygon.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Yaqiang Wang, 3 | * yaqiang.wang@gmail.com 4 | */ 5 | package contour.draw.spatial; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | import contour.draw.Contour; 12 | 13 | /** 14 | * Polygon class 15 | * 16 | * @author Yaqiang Wang 17 | */ 18 | public class Polygon { 19 | 20 | // 21 | /** 22 | * If is border contour polygon 23 | */ 24 | public boolean IsBorder; 25 | /** 26 | * If there is only inner border 27 | */ 28 | public boolean IsInnerBorder = false; 29 | /** 30 | * Start value 31 | */ 32 | public double LowValue; 33 | /** 34 | * End value 35 | */ 36 | public double HighValue; 37 | /** 38 | * If clockwise 39 | */ 40 | public boolean IsClockWise; 41 | /** 42 | * Start point index 43 | */ 44 | public int StartPointIdx; 45 | 46 | /** 47 | * Is high center or not 48 | */ 49 | public boolean IsHighCenter; 50 | 51 | /** 52 | * Extent - bordering rectangle 53 | */ 54 | public Extent Extent = new Extent(); 55 | 56 | /** 57 | * Area 58 | */ 59 | public double Area; 60 | 61 | /** 62 | * Outline 63 | */ 64 | public PolyLine OutLine = new PolyLine(); 65 | 66 | /** 67 | * Hole lines 68 | */ 69 | public List HoleLines = new ArrayList<>(); 70 | 71 | /** 72 | * Hole index 73 | */ 74 | public int HoleIndex; 75 | 76 | // 77 | // 78 | /** 79 | * Clone the object 80 | * 81 | * @return cloned Polygon object 82 | */ 83 | public Object Clone() { 84 | Polygon aPolygon = new Polygon(); 85 | aPolygon.IsBorder = IsBorder; 86 | aPolygon.LowValue = LowValue; 87 | aPolygon.HighValue = HighValue; 88 | aPolygon.IsClockWise = IsClockWise; 89 | aPolygon.StartPointIdx = StartPointIdx; 90 | aPolygon.IsHighCenter = IsHighCenter; 91 | aPolygon.Extent = Extent; 92 | aPolygon.Area = Area; 93 | aPolygon.OutLine = OutLine; 94 | aPolygon.HoleLines = new ArrayList<>(HoleLines); 95 | aPolygon.HoleIndex = HoleIndex; 96 | 97 | return aPolygon; 98 | } 99 | 100 | /** 101 | * Get if has holes 102 | * 103 | * @return Boolean 104 | */ 105 | public boolean HasHoles() { 106 | return (HoleLines.size() > 0); 107 | } 108 | 109 | /** 110 | * Add a pohygon hole 111 | * 112 | * @param aPolygon The polygon hole 113 | */ 114 | public void AddHole(Polygon aPolygon) { 115 | HoleLines.add(aPolygon.OutLine); 116 | } 117 | 118 | /** 119 | * Add a hole by point list 120 | * 121 | * @param pList The point list 122 | */ 123 | public void AddHole(List pList) { 124 | if (Contour.isClockwise(pList)) { 125 | Collections.reverse(pList); 126 | } 127 | 128 | PolyLine aLine = new PolyLine(); 129 | aLine.PointList = pList; 130 | HoleLines.add(aLine); 131 | } 132 | // 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/contour/utils/CsvParser.java: -------------------------------------------------------------------------------- 1 | package contour.utils; 2 | 3 | import java.io.File; 4 | import java.nio.charset.Charset; 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import org.apache.commons.csv.CSVFormat; 12 | import org.apache.commons.csv.CSVParser; 13 | import org.apache.commons.csv.CSVRecord; 14 | 15 | 16 | public class CsvParser { 17 | 18 | public static List> parse(String paths) { 19 | List> retList = new ArrayList<>(); 20 | Map shema = new HashMap<>(); 21 | try { 22 | CSVFormat formator = CSVFormat.DEFAULT; 23 | String encodeType = EncodeUtils.getEncode(paths, true); 24 | CSVParser parser = CSVParser.parse(new File(paths),Charset.forName(encodeType),formator); 25 | List records = parser.getRecords(); 26 | boolean head=true; 27 | for (CSVRecord record : records) { 28 | if(head){ 29 | int count = 0; 30 | Iterator it = record.iterator(); 31 | while(it.hasNext()){ 32 | count++; 33 | shema.put(count, it.next()); 34 | } 35 | head = false; 36 | }else{ 37 | Map dataMap = new HashMap<>(); 38 | int count = 0; 39 | Iterator it = record.iterator(); 40 | while(it.hasNext()){ 41 | count++; 42 | dataMap.put(shema.get(count), it.next()); 43 | } 44 | retList.add(dataMap); 45 | } 46 | } 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | } 50 | return retList; 51 | 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/contour/utils/EncodeUtils.java: -------------------------------------------------------------------------------- 1 | package contour.utils; 2 | import java.io.*; 3 | import java.util.BitSet; 4 | /** 5 | * 编码工具类,主要用于识别UTF8、UTF8 BOM、GBK 6 | */ 7 | 8 | public class EncodeUtils { 9 | 10 | private static int BYTE_SIZE = 8; 11 | public static String CODE_UTF8 = "UTF-8"; 12 | public static String CODE_UTF8_BOM = "UTF-8_BOM"; 13 | public static String CODE_GBK = "GBK"; 14 | 15 | /** 16 | * 通过文件全名称获取编码集名称 17 | * 18 | * @param fullFileName 19 | * @param ignoreBom 20 | * @return 21 | * @throws Exception 22 | */ 23 | public static String getEncode(String fullFileName, boolean ignoreBom) throws Exception { 24 | BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fullFileName)); 25 | return getEncode(bis, ignoreBom); 26 | } 27 | 28 | /** 29 | * 通过文件缓存流获取编码集名称,文件流必须为未曾 30 | * 31 | * @param bis 32 | * @return 33 | * @throws Exception 34 | */ 35 | public static String getEncode(BufferedInputStream bis, boolean ignoreBom) throws Exception { 36 | bis.mark(0); 37 | 38 | String encodeType = ""; 39 | byte[] head = new byte[3]; 40 | bis.read(head); 41 | if (head[0] == -1 && head[1] == -2) { 42 | encodeType = "UTF-16"; 43 | } else if (head[0] == -2 && head[1] == -1) { 44 | encodeType = "Unicode"; 45 | } else if (head[0] == -17 && head[1] == -69 && head[2] == -65) { //带BOM 46 | if (ignoreBom) { 47 | encodeType = CODE_UTF8; 48 | } else { 49 | encodeType = CODE_UTF8_BOM; 50 | } 51 | } else if ("Unicode".equals(encodeType)) { 52 | encodeType = "UTF-16"; 53 | } else if (isUTF8(bis)) { 54 | encodeType = CODE_UTF8; 55 | } else { 56 | encodeType = CODE_GBK; 57 | } 58 | return encodeType; 59 | } 60 | 61 | /** 62 | * 是否是无BOM的UTF8格式,不判断常规场景,只区分无BOM UTF8和GBK 63 | * 64 | * @param bis 65 | * @return 66 | */ 67 | private static boolean isUTF8( BufferedInputStream bis) throws Exception { 68 | bis.reset(); 69 | 70 | //读取第一个字节 71 | int code = bis.read(); 72 | do { 73 | BitSet bitSet = convert2BitSet(code); 74 | //判断是否为单字节 75 | if (bitSet.get(0)) {//多字节时,再读取N个字节 76 | if (!checkMultiByte(bis, bitSet)) {//未检测通过,直接返回 77 | return false; 78 | } 79 | } else { 80 | //单字节时什么都不用做,再次读取字节 81 | } 82 | code = bis.read(); 83 | } while (code != -1); 84 | return true; 85 | } 86 | 87 | /** 88 | * 检测多字节,判断是否为utf8,已经读取了一个字节 89 | * 90 | * @param bis 91 | * @param bitSet 92 | * @return 93 | */ 94 | private static boolean checkMultiByte( BufferedInputStream bis, BitSet bitSet) throws Exception { 95 | int count = getCountOfSequential(bitSet); 96 | byte[] bytes = new byte[count - 1];//已经读取了一个字节,不能再读取 97 | bis.read(bytes); 98 | for (byte b : bytes) { 99 | if (!checkUtf8Byte(b)) { 100 | return false; 101 | } 102 | } 103 | return true; 104 | } 105 | 106 | /** 107 | * 检测单字节,判断是否为utf8 108 | * 109 | * @param b 110 | * @return 111 | */ 112 | private static boolean checkUtf8Byte(byte b) throws Exception { 113 | BitSet bitSet = convert2BitSet(b); 114 | return bitSet.get(0) && !bitSet.get(1); 115 | } 116 | 117 | /** 118 | * 检测bitSet中从开始有多少个连续的1 119 | * 120 | * @param bitSet 121 | * @return 122 | */ 123 | private static int getCountOfSequential( BitSet bitSet) { 124 | int count = 0; 125 | for (int i = 0; i < BYTE_SIZE; i++) { 126 | if (bitSet.get(i)) { 127 | count++; 128 | } else { 129 | break; 130 | } 131 | } 132 | return count; 133 | } 134 | 135 | 136 | /** 137 | * 将整形转为BitSet 138 | * 139 | * @param code 140 | * @return 141 | */ 142 | private static BitSet convert2BitSet(int code) { 143 | BitSet bitSet = new BitSet(BYTE_SIZE); 144 | 145 | for (int i = 0; i < BYTE_SIZE; i++) { 146 | int tmp3 = code >> (BYTE_SIZE - i - 1); 147 | int tmp2 = 0x1 & tmp3; 148 | if (tmp2 == 1) { 149 | bitSet.set(i); 150 | } 151 | } 152 | return bitSet; 153 | } 154 | 155 | /** 156 | * 将一指定编码的文件转换为另一编码的文件 157 | * 158 | * @param oldFullFileName 159 | * @param oldCharsetName 160 | * @param newFullFileName 161 | * @param newCharsetName 162 | */ 163 | public static void convert(String oldFullFileName, String oldCharsetName, String newFullFileName, String newCharsetName) throws Exception { 164 | 165 | StringBuffer content = new StringBuffer(); 166 | 167 | 168 | BufferedReader bin = new BufferedReader(new InputStreamReader(new FileInputStream(oldFullFileName), oldCharsetName)); 169 | String line; 170 | while ((line = bin.readLine()) != null) { 171 | content.append(line); 172 | content.append(System.getProperty("line.separator")); 173 | } 174 | newFullFileName = newFullFileName.replace("\\", "/"); 175 | File dir = new File(newFullFileName.substring(0, newFullFileName.lastIndexOf("/"))); 176 | if (!dir.exists()) { 177 | dir.mkdirs(); 178 | } 179 | 180 | Writer out = new OutputStreamWriter(new FileOutputStream(newFullFileName), newCharsetName); 181 | out.write(content.toString()); 182 | 183 | if(out!=null){ 184 | out.close(); 185 | } 186 | if(bin!=null){ 187 | bin.close(); 188 | } 189 | } 190 | 191 | } 192 | 193 | -------------------------------------------------------------------------------- /src/main/java/contour/utils/MathsUtil.java: -------------------------------------------------------------------------------- 1 | package contour.utils; 2 | 3 | /** 4 | * MathsUtil 5 | * @author xuwei 6 | */ 7 | public class MathsUtil { 8 | 9 | public static double[] kriging_matrix_diag(final double c, final int n) { 10 | int i = 0; 11 | final double[] Z = new double[n * n]; 12 | for (i = 0; i < n; i++) { 13 | Z[i * n + i] = c; 14 | } 15 | return Z; 16 | } 17 | 18 | public static double[] kriging_matrix_transpose(final double[] X, final int n, final int m) { 19 | int i, j; 20 | final double[] Z = new double[m * n]; 21 | for (i = 0; i < n; i++) { 22 | for (j = 0; j < m; j++) { 23 | Z[j * n + i] = X[i * m + j]; 24 | } 25 | } 26 | return Z; 27 | } 28 | 29 | public static void kriging_matrix_scale(double[] X, double c, int n, int m) { 30 | int i =0, j = 0; 31 | for (i = 0; i < n; i++){ 32 | for (j = 0; j < m; j++) { 33 | X[i * m + j] *= c; 34 | } 35 | } 36 | } 37 | 38 | public static double[] kriging_matrix_add(final double[] X, final double[] Y, final int n, final int m) { 39 | int i, j; 40 | final double[] Z = new double[n * m]; 41 | for (i = 0; i < n; i++) 42 | for (j = 0; j < m; j++) 43 | Z[i * m + j] = X[i * m + j] + Y[i * m + j]; 44 | return Z; 45 | } 46 | 47 | // Naive matrix multiplication 48 | public static double[] kriging_matrix_multiply(final double[] X, final double[] Y, final int n, final int m, final int p) { 49 | int i, j, k; 50 | final double[] Z = new double[n * p]; 51 | for (i = 0; i < n; i++) { 52 | for (j = 0; j < p; j++) { 53 | Z[i * p + j] = 0; 54 | for (k = 0; k < m; k++){ 55 | Z[i * p + j] += X[i * m + k] * Y[k * p + j]; 56 | } 57 | } 58 | } 59 | return Z; 60 | } 61 | 62 | // Cholesky decomposition 63 | public static boolean kriging_matrix_chol(final double[] X, final int n) { 64 | int i, j, k; 65 | final double[] p = new double[n]; 66 | for (i = 0; i < n; i++) { 67 | p[i] = X[i * n + i]; 68 | } 69 | for (i = 0; i < n; i++) { 70 | for (j = 0; j < i; j++) { 71 | p[i] -= X[i * n + j] * X[i * n + j]; 72 | } 73 | 74 | if (p[i] <= 0) { 75 | return false; 76 | } 77 | 78 | p[i] = Math.sqrt(p[i]); 79 | for (j = i + 1; j < n; j++) { 80 | for (k = 0; k < i; k++) { 81 | X[j * n + i] -= X[j * n + k] * X[i * n + k]; 82 | } 83 | X[j * n + i] /= p[i]; 84 | } 85 | } 86 | for (i = 0; i < n; i++) { 87 | X[i * n + i] = p[i]; 88 | } 89 | return true; 90 | } 91 | 92 | // Inversion of cholesky decomposition 93 | public static void kriging_matrix_chol2inv(final double[] X, final int n) { 94 | int i, j, k; 95 | double sum; 96 | for (i = 0; i < n; i++) { 97 | X[i * n + i] = 1 / X[i * n + i]; 98 | for (j = i + 1; j < n; j++) { 99 | sum = 0; 100 | for (k = i; k < j; k++){ 101 | sum -= X[j * n + k] * X[k * n + i]; 102 | } 103 | X[j * n + i] = sum / X[j * n + j]; 104 | } 105 | } 106 | for (i = 0; i < n; i++){ 107 | for (j = i + 1; j < n; j++){ 108 | X[i * n + j] = 0; 109 | } 110 | } 111 | for (i = 0; i < n; i++) { 112 | X[i * n + i] *= X[i * n + i]; 113 | for (k = i + 1; k < n; k++){ 114 | X[i * n + i] += X[k * n + i] * X[k * n + i]; 115 | } 116 | for (j = i + 1; j < n; j++){ 117 | for (k = j; k < n; k++){ 118 | X[i * n + j] += X[k * n + i] * X[k * n + j]; 119 | } 120 | } 121 | } 122 | for (i = 0; i < n; i++){ 123 | for (j = 0; j < i; j++){ 124 | X[i * n + j] = X[j * n + i]; 125 | } 126 | } 127 | } 128 | 129 | // Inversion via gauss-jordan elimination 130 | public static boolean kriging_matrix_solve(double[] X, int n) { 131 | int m = n; 132 | double[] b = new double[n * n]; 133 | int[] indxc = new int[n]; 134 | int[] indxr = new int[n]; 135 | double[] ipiv = new double[n]; 136 | int i = 0, icol = 0, irow = 0, j = 0, k = 0, l = 0, ll = 0; 137 | double big, dum, pivinv, temp; 138 | 139 | for (i = 0; i < n; i++){ 140 | for (j = 0; j < n; j++) { 141 | if (i == j) { 142 | b[i * n + j] = 1; 143 | } else { 144 | b[i * n + j] = 0; 145 | } 146 | } 147 | } 148 | for (j = 0; j < n; j++) { 149 | ipiv[j] = 0; 150 | } 151 | for (i = 0; i < n; i++) { 152 | big = 0; 153 | for (j = 0; j < n; j++) { 154 | if (ipiv[j] != 1) { 155 | for (k = 0; k < n; k++) { 156 | if (ipiv[k] == 0) { 157 | if (Math.abs(X[j * n + k]) >= big) { 158 | big = Math.abs(X[j * n + k]); 159 | irow = j; 160 | icol = k; 161 | } 162 | } 163 | } 164 | } 165 | } 166 | ++(ipiv[icol]); 167 | 168 | if (irow != icol) { 169 | for (l = 0; l < n; l++) { 170 | temp = X[irow * n + l]; 171 | X[irow * n + l] = X[icol * n + l]; 172 | X[icol * n + l] = temp; 173 | } 174 | for (l = 0; l < m; l++) { 175 | temp = b[irow * n + l]; 176 | b[irow * n + l] = b[icol * n + l]; 177 | b[icol * n + l] = temp; 178 | } 179 | } 180 | indxr[i] = irow; 181 | indxc[i] = icol; 182 | 183 | if (X[icol * n + icol] == 0) return false; // Singular 184 | 185 | pivinv = 1 / X[icol * n + icol]; 186 | X[icol * n + icol] = 1; 187 | for (l = 0; l < n; l++) X[icol * n + l] *= pivinv; 188 | for (l = 0; l < m; l++) b[icol * n + l] *= pivinv; 189 | 190 | for (ll = 0; ll < n; ll++) { 191 | if (ll != icol) { 192 | dum = X[ll * n + icol]; 193 | X[ll * n + icol] = 0; 194 | for (l = 0; l < n; l++) X[ll * n + l] -= X[icol * n + l] * dum; 195 | for (l = 0; l < m; l++) b[ll * n + l] -= b[icol * n + l] * dum; 196 | } 197 | } 198 | } 199 | for (l = (n - 1); l >= 0; l--) { 200 | if (indxr[l] != indxc[l]) { 201 | for (k = 0; k < n; k++) { 202 | temp = X[k * n + indxr[l]]; 203 | X[k * n + indxr[l]] = X[k * n + indxc[l]]; 204 | X[k * n + indxc[l]] = temp; 205 | } 206 | } 207 | } 208 | return true; 209 | } 210 | } -------------------------------------------------------------------------------- /src/main/java/contour/utils/SphericalMercator.java: -------------------------------------------------------------------------------- 1 | package contour.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Web Mercator 8 | */ 9 | public class SphericalMercator { 10 | public final static int earthDiameter = 1275674; 11 | public final static double deg2rad = Math.PI / 180; 12 | public final static double rad2deg = 180 / Math.PI; 13 | public final static double quadPI = Math.PI / 4; 14 | public final static double half2PI = 1 / (2 * Math.PI); 15 | private Map scaleCache = new HashMap<>(); 16 | 17 | 18 | public double getScale(int level) { 19 | if(!scaleCache.containsKey(level)){ 20 | scaleCache.put(level, 256*Math.pow(2, level)); 21 | } 22 | return scaleCache.get(level); 23 | } 24 | 25 | public double[] project(double[] lngLatArr) { 26 | double x = lngLatArr[0] * deg2rad, 27 | y = lngLatArr[1] * deg2rad; 28 | y = Math.log(Math.tan(quadPI + y / 2)); 29 | return new double[]{x, y}; 30 | } 31 | 32 | private double[] transform(double[] point, double scale) { 33 | double a = half2PI, b = 0.5, c = -a, d = 0.5; 34 | return new double[]{ 35 | scale * (a * point[0] + b), 36 | scale * (c * point[1] + d) 37 | }; 38 | } 39 | 40 | public double[] unproject(double[] point) { 41 | double lng = point[0] * rad2deg, 42 | lat = (2 * Math.atan(Math.exp(point[1])) - Math.PI/2)*rad2deg; 43 | return new double[] { 44 | Math.round(lng * 1000000) / 1000000d, 45 | Math.round(lat * 1000000) / 1000000d 46 | }; 47 | } 48 | 49 | private double[] untransform(double[] point, double scale) { 50 | double a = half2PI, 51 | b = 0.5, 52 | c = -a, 53 | d = 0.5; 54 | return new double[] {(point[0] / scale-b) / a, (point[1] / scale-d) / c}; 55 | } 56 | 57 | public double[] lngLatToPointByScale(double[] lngLatArr, double scale, boolean isRound) { 58 | double[] p = this.transform(this.project(lngLatArr), scale); 59 | if(isRound){ 60 | p[0] = Math.round(p[0]); 61 | p[1] = Math.round(p[1]); 62 | } 63 | return p; 64 | } 65 | 66 | public double[] lngLatToPoint(double[] lngLatArr, int level, boolean isRound){ 67 | return lngLatToPointByScale(lngLatArr, this.getScale(level), isRound); 68 | } 69 | 70 | public double[] pointToLngLat(double[] point, int level) { 71 | double[] untransformedPoint = this.untransform(point, getScale(level)); 72 | return unproject(untransformedPoint); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # Set everything to be logged to the console 19 | log4j.rootCategory=DEBUG, console 20 | log4j.appender.console=org.apache.log4j.ConsoleAppender 21 | log4j.appender.console.target=System.err 22 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 23 | log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n 24 | 25 | # Set the default spark-shell log level to WARN. When running the spark-shell, the 26 | # log level for this class is used to overwrite the root logger's log level, so that 27 | # the user can have different defaults for the shell and regular Spark apps. 28 | log4j.logger.org.apache.spark.repl.Main=WARN 29 | 30 | # Settings to quiet third party logs that are too verbose 31 | log4j.logger.org.spark_project.jetty=WARN 32 | log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR 33 | log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=INFO 34 | log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=INFO 35 | 36 | # SPARK-9183: Settings to avoid annoying messages when looking up nonexistent UDFs in SparkSQL with Hive support 37 | log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=FATAL 38 | log4j.logger.org.apache.hadoop.hive.ql.exec.FunctionRegistry=ERROR 39 | 40 | # Parquet related logging 41 | log4j.logger.org.apache.parquet.CorruptStatistics=ERROR 42 | log4j.logger.parquet.CorruptStatistics=ERROR 43 | -------------------------------------------------------------------------------- /src/test/java/color/ColorTest.java: -------------------------------------------------------------------------------- 1 | package color; 2 | 3 | import java.awt.Color; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.junit.Test; 8 | 9 | import contour.bean.Tuple5; 10 | import contour.utils.ColorUtils; 11 | import contour.utils.InterpolateRgb; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | public class ColorTest { 16 | private Logger logger = LoggerFactory.getLogger("ColorTest"); 17 | 18 | @Test 19 | public void test1() { 20 | System.out.println(Color.RED); 21 | String hexString = ColorUtils.colorToHexValue(Color.RED); 22 | System.out.println("16进制字符串:" + hexString); 23 | Color color = ColorUtils.covertHexColorToRGB(hexString); 24 | System.out 25 | .println("16进制字符串转为颜色的ARGB值:(" + String.valueOf(color.getAlpha()) + "," + String.valueOf(color.getRed()) 26 | + "," + String.valueOf(color.getGreen()) + "," + String.valueOf(color.getBlue()) + ")"); 27 | } 28 | 29 | @Test 30 | public void test2() { 31 | String hexColor = "#00E400"; 32 | Color color = ColorUtils.covertHexColorToRGB(hexColor.substring(1)); 33 | System.out.println("rgb(" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + ")"); 34 | } 35 | 36 | /** 37 | * 颜色插值测试 38 | */ 39 | @Test 40 | public void test3() { 41 | String[] baseColors = { "#0021FF", "#00FCFE", "#07FD00", "#FFF40B", "#FF000E" }; 42 | // System.out.println(covertHexColorToRGB(baseColors[0])); 43 | List colorList = new ArrayList<>(100); 44 | InterpolateRgb interpolate = null; 45 | for (int i = 0; i < baseColors.length - 1; i++) { 46 | Color startColor = ColorUtils.covertHexColorToRGB(baseColors[i]); 47 | Color endColor = ColorUtils.covertHexColorToRGB(baseColors[i + 1]); 48 | interpolate = new InterpolateRgb(startColor, endColor); 49 | for (int j = 0; j <= 100; j++) { 50 | Color color = interpolate.get((float) j / 100); 51 | int[] rgbArr = new int[] { color.getRed(), color.getGreen(), color.getBlue() }; 52 | colorList.add(rgbArr); 53 | } 54 | } 55 | 56 | System.out.println(colorList.get(colorList.size() - 1)); 57 | 58 | } 59 | 60 | 61 | @Test 62 | public void test4() { 63 | // Color beginColor = ColorUtils.covertHexColorToRGB("#0021FF"); 64 | // Color endColor = ColorUtils.covertHexColorToRGB("#00FCFE"); 65 | Color sColor = new Color(0,228,0); 66 | Color eColor = new Color(255,255,9); 67 | List> list = ColorUtils.buildInterpolationColors(sColor, eColor, new int[]{50, 100}, 10); 68 | logger.info("VALUE_MIN,VALUE_MAX,R,G,B"); 69 | for(Tuple5 t : list){ 70 | logger.info(t._1+","+t._2+"|"+t._3+","+t._4+","+t._5); 71 | } 72 | 73 | } 74 | 75 | 76 | } -------------------------------------------------------------------------------- /src/test/java/contour/IDW/IDWImage.java: -------------------------------------------------------------------------------- 1 | package contour.IDW; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.LoggerFactory; 6 | 7 | import contour.bean.Tuple5; 8 | import contour.common.AbstractImage; 9 | 10 | /**等值线图片工具类, 主要负责根据算法得到的数据进行等值线(面)的绘制工作 11 | * ContourImage 12 | * @author xuwei 13 | */ 14 | public class IDWImage extends AbstractImage{ 15 | 16 | public IDWImage(double[][] rawdata, 17 | List> colors, 18 | double[][] bounds, 19 | String filePath, 20 | String mapDataPath, 21 | int zoom 22 | ) { 23 | 24 | super(rawdata, colors, bounds, filePath, mapDataPath, zoom); 25 | 26 | this.logger = LoggerFactory.getLogger(IDWImage.class); 27 | 28 | //开始插值,生成等值面图片 29 | IDWutil idWutil = new IDWutil(rawdata, colorValues, left, right, top, bottom); 30 | this.contourPolygons = idWutil.interpolate(); 31 | } 32 | 33 | 34 | } -------------------------------------------------------------------------------- /src/test/java/contour/IDW/IDWTest.java: -------------------------------------------------------------------------------- 1 | package contour.IDW; 2 | 3 | import contour.bean.Tuple5; 4 | import contour.utils.ColorUtils; 5 | import contour.utils.CsvParser; 6 | import contour.utils.FileUtils; 7 | import org.junit.Test; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.awt.*; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * IDWTest 18 | */ 19 | public class IDWTest { 20 | private Logger logger = LoggerFactory.getLogger("IDWTest"); 21 | 22 | @Test 23 | public void testBuildContourImage() { 24 | int zoom = 9; 25 | String inputPath = "input/"; 26 | String fileName = "data.csv"; 27 | String outputPath = FileUtils.getDiskRootPath() + "/Tmp/"; 28 | String borderDataPath = "input/border.csv"; 29 | double[][] bounds = new double[][]{{116.760922, 23.391427}, {118.364926, 25.402349}}; 30 | List> colors = buildAQIColors(); 31 | logger.info("VALUE_MIN,VALUE_MAX,R,G,B"); 32 | for(Tuple5 t : colors){ 33 | logger.info(t._1+","+t._2+"|"+t._3+","+t._4+","+t._5); 34 | } 35 | double[][] rawdata = getData(inputPath, fileName); 36 | long time = System.currentTimeMillis(); 37 | IDWImage idwImage = new IDWImage(rawdata, colors, bounds, outputPath + "idw_contour", borderDataPath, zoom); 38 | logger.info("Interpolation cost time: "+ (System.currentTimeMillis() - time)); 39 | idwImage.setClipBounds(false); 40 | idwImage.draw(); 41 | } 42 | 43 | private double[][] getData(String path, String fileName) { 44 | String dataPath = this.getClass().getClassLoader() 45 | .getResource(path+"/"+fileName).getPath(); 46 | List> dataList = CsvParser.parse(dataPath); 47 | double[][] retList = new double[dataList.size()][3]; 48 | for (int i = 0; i < dataList.size(); i++) { 49 | Map map = dataList.get(i); 50 | if("null".equals(map.get("VALUE"))) { 51 | continue; 52 | } 53 | Double lon = Double.parseDouble(map.get("LON").trim()); 54 | Double lat = Double.parseDouble(map.get("LAT").trim()); 55 | Double value = Double.parseDouble(map.get("VALUE").trim()); 56 | retList[i][0] = lon; 57 | retList[i][1] = lat; 58 | retList[i][2] = value; 59 | } 60 | return retList; 61 | } 62 | 63 | public List> buildAQIColors() { 64 | List> retList = new ArrayList<>(); 65 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(0,228,0), new Color(255,255,9), new int[]{0, 50}, 10)); 66 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(255,255,9), new Color(255,126,0), new int[]{50, 100}, 15)); 67 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(255,126,0), new Color(255,0,0), new int[]{100, 150}, 20)); 68 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(255,0,0), new Color(153,0,76), new int[]{150, 200}, 20)); 69 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(153,0,76), new Color(126,0,35), new int[]{200, 300}, 20)); 70 | retList.add(new Tuple5(300D, 3000D, 126, 0, 35)); 71 | return retList; 72 | } 73 | 74 | 75 | } -------------------------------------------------------------------------------- /src/test/java/contour/IDW/IDWutil.java: -------------------------------------------------------------------------------- 1 | package contour.IDW; 2 | 3 | import java.util.Collections; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import contour.draw.Contour; 11 | import contour.draw.spatial.Border; 12 | import contour.draw.spatial.PolyLine; 13 | import contour.draw.spatial.Polygon; 14 | import contour.algorithm.IDW; 15 | 16 | /** 17 | * IDWutil 18 | * 19 | * @author xuwei 20 | */ 21 | public class IDWutil { 22 | 23 | private Logger logger = LoggerFactory.getLogger(IDWutil.class); 24 | 25 | private static final int DEFAULT_ALGORITHM_ROWS = 200; 26 | private static final int DEFAULT_ALGORITHM_COLS = 200; 27 | private static final double DEFAULT_ALGORITHM_UNDEFINE = -9999.0; 28 | 29 | //data[0]--> longitude array, data[1]-->latitude array, data[2]-->kpi data array 30 | private double[][] data; 31 | 32 | private double[] colorValues; 33 | 34 | private double left, right, top, bottom; 35 | 36 | public IDWutil(double[][] data, double[] colorValues, double left, double right, double top, double bottom){ 37 | this.data = data; 38 | this.colorValues = colorValues; 39 | this.left = left; 40 | this.right = right; 41 | this.top = top; 42 | this.bottom = bottom; 43 | } 44 | 45 | public List interpolate() { 46 | logger.info("IDW算法开始插值..."); 47 | double[] x = new double[DEFAULT_ALGORITHM_ROWS]; 48 | double[] y = new double[DEFAULT_ALGORITHM_COLS]; 49 | int neighborNumber = colorValues.length - 1; 50 | neighborNumber = 10; 51 | if(neighborNumber>data.length){ 52 | neighborNumber = data.length; 53 | } 54 | 55 | // 填充数据 56 | IDW.createGridXY_Num(left, bottom, right, top, x, y); 57 | // double[][] gridData = IDW.interpolation_IDW_Neighbor( 58 | // data, x, y, neighborNumber, DEFAULT_ALGORITHM_UNDEFINE); 59 | double[][] gridData = IDW.interpolation_IDW_Radius( 60 | data, x, y, neighborNumber, 100, DEFAULT_ALGORITHM_UNDEFINE); 61 | // double[][] gridData = IDW.interpolation_IDW(data, x, y, 15, 10, 0.01585995, 0.01585995, 0, 0 ); 62 | 63 | int nc = colorValues.length; 64 | int[][] S1 = new int[gridData.length][gridData[0].length]; 65 | 66 | List borders = Contour.tracingBorders(gridData, x, y, S1, DEFAULT_ALGORITHM_UNDEFINE); 67 | List contourLines = Contour.tracingContourLines(gridData, x, y, nc, 68 | colorValues, DEFAULT_ALGORITHM_UNDEFINE, borders, S1); 69 | 70 | // 平滑处理 71 | contourLines = Contour.smoothLines(contourLines); 72 | 73 | List contourPolygons = Contour.tracingPolygons(gridData, contourLines, 74 | borders, colorValues); 75 | Collections.sort(contourPolygons, new Comparator() { 76 | @Override 77 | public int compare(Polygon o1, Polygon o2) { 78 | return Double.compare(o2.Area, o1.Area); 79 | } 80 | }); 81 | 82 | 83 | return contourPolygons; 84 | } 85 | 86 | 87 | 88 | 89 | } -------------------------------------------------------------------------------- /src/test/java/contour/common/AbstractImage.java: -------------------------------------------------------------------------------- 1 | package contour.common; 2 | 3 | import java.awt.AlphaComposite; 4 | import java.awt.BasicStroke; 5 | import java.awt.Color; 6 | import java.awt.Font; 7 | import java.awt.Graphics2D; 8 | import java.awt.RenderingHints; 9 | import java.awt.Transparency; 10 | import java.awt.image.BufferedImage; 11 | import java.io.File; 12 | import java.io.FileOutputStream; 13 | import java.io.IOException; 14 | import java.io.OutputStream; 15 | import java.util.LinkedHashMap; 16 | import java.util.List; 17 | 18 | import javax.imageio.ImageIO; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import contour.bean.Tuple5; 24 | import contour.draw.spatial.PointD; 25 | import contour.draw.spatial.PolyLine; 26 | import contour.draw.spatial.Polygon; 27 | import contour.utils.MapUtils; 28 | import contour.utils.SphericalMercator; 29 | 30 | /**等值线图片工具类, 主要负责根据算法得到的数据进行等值线(面)的绘制工作 31 | * ContourImage 32 | * @author xuwei 33 | */ 34 | public abstract class AbstractImage { 35 | protected Logger logger = LoggerFactory.getLogger(AbstractImage.class); 36 | // 经纬度以 “点 ” 的形式展示控制 37 | private DrawStyle stationStyle = new DrawStyle(false, 10, Color.RED); 38 | // 等值线值 显示控制、大小控制 39 | private DrawStyle line_value_style = new DrawStyle(false, 40, Color.BLACK); 40 | // 等值线是否绘制 、样式控制 41 | private DrawStyle line_style = new DrawStyle(false, 1, Color.ORANGE); 42 | // 是否填充等值线 43 | private boolean fillContour = true; 44 | //是否显示点位值 45 | private boolean showPointValue = true; 46 | // 色标 47 | private Color[] colorArray; 48 | // 色标值 49 | protected double[] colorValues; 50 | //是否裁剪 51 | private boolean clipBounds = true; 52 | 53 | List> outLine; 54 | 55 | private int width; 56 | 57 | private int height; 58 | 59 | protected double top; 60 | 61 | protected double left; 62 | 63 | protected double bottom; 64 | 65 | protected double right; 66 | 67 | private int zoom; 68 | 69 | private SphericalMercator mercator = new SphericalMercator(); 70 | 71 | protected double[][] data; 72 | 73 | private String mapDataPath; 74 | 75 | protected List contourPolygons; 76 | private LinkedHashMap colorMap; 77 | private String filePath; 78 | 79 | public AbstractImage(double[][] rawdata, 80 | List> colors, 81 | double[][] bounds, 82 | String filePath, 83 | String mapDataPath, 84 | int zoom 85 | ) { 86 | this.filePath = filePath; 87 | this.left = bounds[0][0]; 88 | this.bottom = bounds[0][1]; 89 | this.right = bounds[1][0]; 90 | this.top = bounds[1][1]; 91 | 92 | this.data = rawdata; 93 | this.mapDataPath = mapDataPath; 94 | this.zoom = zoom; 95 | double[] southwestPixel = this.mercator.lngLatToPoint(new double[]{left, bottom}, zoom, false); 96 | double[] northEastPixel = this.mercator.lngLatToPoint(new double[]{right, top}, zoom, false); 97 | this.width = (int) (northEastPixel[0] - southwestPixel[0]); 98 | this.height = (int) (southwestPixel[1] - northEastPixel[1]); 99 | 100 | colorDeal(colors); 101 | 102 | //开始插值,生成等值面图片 103 | // IDWutil idWutil = new IDWutil(rawdata, colorValues, left, right, top, bottom); 104 | // this.contourPolygons = idWutil.interpolate(); 105 | } 106 | 107 | public void draw(){ 108 | if(this.clipBounds){ 109 | this.outLine = MapUtils.readMapData(mapDataPath); 110 | } 111 | String tmpPath = this.filePath + "_tmp"; 112 | try { 113 | logger.info("paint basic picture ..."); 114 | drawBasic(tmpPath); 115 | logger.info("paint contour picture ..."); 116 | if(this.clipBounds){ 117 | drawContourByClip(filePath, tmpPath); 118 | }else{ 119 | drawContour(filePath, tmpPath); 120 | } 121 | } catch (IOException e) { 122 | e.printStackTrace(); 123 | } 124 | } 125 | 126 | // 处理色标数据 127 | private void colorDeal(List> colors) { 128 | colorArray = new Color[colors.size()]; 129 | colorValues = new double[colors.size()]; 130 | int count = 0; 131 | for (Tuple5 color : colors) { 132 | double value_min = color._1; 133 | colorValues[count] = value_min; 134 | // colorArray[count] = new Color(color._3, color._4, color._5); 135 | colorArray[count] = new Color(color._3, color._4, color._5, 150); 136 | count++; 137 | } 138 | colorMap = new LinkedHashMap<>(); 139 | for (int i = 0, len = colorValues.length; i < len; i++) { 140 | colorMap.put(colorValues[i], colorArray[i]); 141 | } 142 | } 143 | 144 | // 绘制底图 145 | public void drawBasic(String basicFile) throws IOException { 146 | BufferedImage base = transparencyImage(Transparency.BITMASK); 147 | Graphics2D g_base = base.createGraphics(); 148 | // 填充边界线 149 | if (outLine != null && outLine.size() > 0) { 150 | borderPolygon(g_base, outLine, Color.WHITE); 151 | } 152 | OutputStream tmpStream = new FileOutputStream(new File(basicFile + ".png")); 153 | ImageIO.write(base, "png", tmpStream); 154 | tmpStream.close(); 155 | g_base.dispose(); 156 | base.flush(); 157 | } 158 | 159 | public void drawContour(String realPath, String tmpPath) throws IOException { 160 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 161 | Graphics2D g2 = image.createGraphics(); 162 | // 抗锯齿处理 163 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 164 | RenderingHints.VALUE_ANTIALIAS_ON); 165 | // 绘制等值面以及等值线 166 | if ((fillContour || line_style.show) && contourPolygons.size() > 0){ 167 | drawPolygon(g2, contourPolygons); 168 | } 169 | if(stationStyle.show){ 170 | drawStation(g2); 171 | } 172 | // 重新打开等值面区域图像 173 | File file = new File(tmpPath + ".png"); 174 | // 图片装入内存 175 | BufferedImage src = ImageIO.read(file); 176 | g2.drawImage(src, 0, 0, width, height, null); 177 | src.flush(); 178 | src = null; 179 | // 删除临时文件 180 | file.delete(); 181 | // 释放对象 182 | g2.dispose(); 183 | // 保存文件 184 | OutputStream out = new FileOutputStream(new File(realPath 185 | + ".png")); 186 | ImageIO.write(image, "png", out); 187 | out.close(); 188 | image.flush(); 189 | logger.info("图片路径: " + realPath + ".png"); 190 | } 191 | 192 | public void drawContourByClip(String realPath, String tmpPath) throws IOException { 193 | BufferedImage image = transparencyImage(Transparency.TRANSLUCENT); 194 | Graphics2D g2 = image.createGraphics(); 195 | // 抗锯齿处理 196 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 197 | RenderingHints.VALUE_ANTIALIAS_ON); 198 | AlphaComposite ac = AlphaComposite 199 | .getInstance(AlphaComposite.SRC_OVER); 200 | g2.setComposite(ac); 201 | 202 | // 绘制等值面以及等值线 203 | if ((fillContour || line_style.show) && contourPolygons.size() > 0){ 204 | drawPolygon(g2, contourPolygons); 205 | } 206 | 207 | if(stationStyle.show){ 208 | drawStation(g2); 209 | } 210 | 211 | // 重新打开等值面区域图像 212 | File file = new File(tmpPath + ".png"); 213 | // 图片装入内存 214 | BufferedImage src = ImageIO.read(file); 215 | ac = AlphaComposite.getInstance(AlphaComposite.DST_IN); 216 | g2.setComposite(ac); 217 | g2.drawImage(src, 0, 0, width, height, null); 218 | src.flush(); 219 | src = null; 220 | // 删除临时文件 221 | file.delete(); 222 | ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER); 223 | g2.setComposite(ac); 224 | // 释放对象 225 | g2.dispose(); 226 | // 保存文件 227 | OutputStream out = new FileOutputStream(new File(realPath 228 | + ".png")); 229 | ImageIO.write(image, "png", out); 230 | out.close(); 231 | image.flush(); 232 | logger.info("图片路径: " + realPath + ".png"); 233 | } 234 | 235 | private BufferedImage transparencyImage(int transparency) { 236 | BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 237 | Graphics2D g2d = bi.createGraphics(); 238 | // 设置画布透明模式 239 | bi = g2d.getDeviceConfiguration().createCompatibleImage(width, height, transparency); 240 | g2d.dispose(); 241 | return bi; 242 | } 243 | 244 | private void borderPolygon(Graphics2D g, List> outLine, 245 | Color fillColor) { 246 | for (List polyLine : outLine) { 247 | polygonLine(g, polyLine, fillColor, Color.BLACK, 5, 0, null); 248 | } 249 | } 250 | 251 | private void polygonLine(Graphics2D g, List outLine, 252 | Color fillColor, Color lineColor, int lineSize, 253 | int fontSize, String msg) { 254 | PointD point; 255 | int len = outLine.size(); 256 | int[] xPoints = new int[len]; 257 | int[] yPoints = new int[len]; 258 | double[] topLeftPixel = this.mercator.lngLatToPoint(new double[]{left, top}, this.zoom, false); 259 | for (int j = 0; j < len; j++) { 260 | point = outLine.get(j); 261 | double[] p = this.mercator.lngLatToPoint(new double[]{point.X, point.Y}, this.zoom, false); 262 | p[0] = p[0] - topLeftPixel[0]; 263 | p[1] = p[1] - topLeftPixel[1]; 264 | xPoints[j] = (int)Math.round(p[0]); 265 | yPoints[j] = (int)Math.round(p[1]); 266 | } 267 | java.awt.Polygon polygon = new java.awt.Polygon(xPoints, yPoints, len); 268 | 269 | // 绘制等值线 270 | if (lineColor != null) { 271 | BasicStroke bs = new BasicStroke(lineSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 272 | g.setStroke(bs); 273 | g.setColor(lineColor); 274 | g.drawPolygon(polygon); 275 | if (msg != null) { 276 | g.setColor(Color.black); 277 | g.setFont(new Font("微软雅黑", Font.BOLD, fontSize)); 278 | g.drawString(msg, xPoints[0], yPoints[0]); 279 | g.drawString(msg, xPoints[len / 2], yPoints[len / 2]); 280 | } 281 | } 282 | 283 | // 填充等值面 284 | if (fillColor != null) { 285 | g.setColor(fillColor); 286 | // g.setColor(null); 287 | g.fillPolygon(polygon); 288 | } 289 | } 290 | 291 | private Color getSpecifyColor(double value){ 292 | Color _color = colorMap.get(value); 293 | if(_color==null){ 294 | for(int i=0, len=colorValues.length; ivalue && i-1 >=0){ 296 | _color = colorArray[i-1]; 297 | break; 298 | } 299 | } 300 | } 301 | return _color; 302 | } 303 | 304 | public void drawPolygon(Graphics2D g, List polygons) { 305 | Color lineColor = line_style.show ? line_style.color : null; 306 | int lineSize = line_style.size; 307 | int n = 0; 308 | for (Polygon polygon : polygons) { 309 | // if(n>5) break; 310 | Color fillColor = fillContour ? getSpecifyColor(polygon.LowValue) : null; 311 | // lineColor = colorMap.get(polygon.LowValue); 312 | if (!polygon.IsHighCenter) { 313 | Color tmp = colorArray[0]; 314 | for (Color c : colorArray) { 315 | if (c == fillColor) { 316 | fillColor = tmp; 317 | break; 318 | } else { 319 | tmp = c; 320 | } 321 | } 322 | } 323 | PolyLine line = polygon.OutLine; 324 | polygonLine(g, line.PointList, fillColor, lineColor, lineSize, line_value_style.size, null); 325 | n++; 326 | } 327 | } 328 | 329 | public void drawStation(Graphics2D g) { 330 | if (data != null) { 331 | double[] topLeftPixel = this.mercator.lngLatToPoint(new double[]{left, top}, this.zoom, false); 332 | int len = data[0].length; 333 | for (int i = 0; i < len; i++) { 334 | double[] p = this.mercator.lngLatToPoint(new double[]{data[0][i], data[1][i]}, this.zoom, false); 335 | p[0] = p[0] - topLeftPixel[0]; 336 | p[1] = p[1] - topLeftPixel[1]; 337 | // g.setColor(stationStyle.color); 338 | // g.fillOval((int)Math.round(p.x), (int)Math.round(p.y), stationStyle.size, stationStyle.size); 339 | if(showPointValue){ 340 | g.setColor(Color.black); 341 | g.setFont(new Font("微软雅黑", Font.PLAIN, 10)); 342 | g.drawString(data[i][2]+"", (int)Math.round(p[0]), (int)Math.round(p[1])); 343 | } 344 | } 345 | } 346 | } 347 | 348 | 349 | class DrawStyle { 350 | public boolean show; 351 | public int size; 352 | public Color color; 353 | 354 | public DrawStyle(boolean show, int size, Color color) { 355 | this.show = show; 356 | this.size = size; 357 | this.color = color; 358 | } 359 | } 360 | 361 | 362 | public boolean isClipBounds() { 363 | return clipBounds; 364 | } 365 | 366 | public void setClipBounds(boolean clipBounds) { 367 | this.clipBounds = clipBounds; 368 | } 369 | 370 | 371 | } -------------------------------------------------------------------------------- /src/test/java/contour/kriging/KrigingImage.java: -------------------------------------------------------------------------------- 1 | package contour.kriging; 2 | 3 | import java.util.Collections; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | 7 | import org.slf4j.LoggerFactory; 8 | 9 | import contour.algorithm.Kriging; 10 | import contour.bean.Tuple5; 11 | import contour.common.AbstractImage; 12 | import contour.draw.Contour; 13 | import contour.draw.spatial.Border; 14 | import contour.draw.spatial.PolyLine; 15 | import contour.draw.spatial.Polygon; 16 | 17 | /**等值线图片工具类, 主要负责根据算法得到的数据进行等值线(面)的绘制工作 18 | * ContourImage 19 | * @author xuwei 20 | */ 21 | public class KrigingImage extends AbstractImage { 22 | 23 | private static final int DEFAULT_ALGORITHM_ROWS = 200; 24 | private static final int DEFAULT_ALGORITHM_COLS = 200; 25 | private static final double DEFAULT_ALGORITHM_UNDEFINE = Double.MIN_VALUE; 26 | 27 | 28 | public KrigingImage(double[][] rawdata, 29 | List> colors, 30 | double[][] bounds, 31 | String filePath, 32 | String mapDataPath, 33 | int zoom 34 | ) { 35 | super(rawdata, colors, bounds, filePath, mapDataPath, zoom); 36 | 37 | this.logger = LoggerFactory.getLogger(KrigingImage.class); 38 | 39 | //开始插值 40 | this.contourPolygons = this.interpolate(); 41 | } 42 | 43 | 44 | private List interpolate(){ 45 | logger.info("普通克里金开始插值..."); 46 | int size = this.data.length; 47 | double[] targetValues = new double[size]; 48 | double[] xList = new double[size]; 49 | double[] yList = new double[size]; 50 | 51 | for(int i=0; i borders = Contour.tracingBorders(gridData, x, y, S1, DEFAULT_ALGORITHM_UNDEFINE); 75 | List contourLines = Contour.tracingContourLines(gridData, x, y, nc, 76 | colorValues, DEFAULT_ALGORITHM_UNDEFINE, borders, S1); 77 | 78 | // 平滑处理 79 | contourLines = Contour.smoothLines(contourLines); 80 | 81 | List contourPolygons = Contour.tracingPolygons(gridData, contourLines, 82 | borders, colorValues); 83 | Collections.sort(contourPolygons, new Comparator() { 84 | @Override 85 | public int compare(Polygon o1, Polygon o2) { 86 | return Double.compare(o2.Area, o1.Area); 87 | } 88 | }); 89 | 90 | return contourPolygons; 91 | } 92 | 93 | 94 | public static void createGridXY_Num(double Xlb, double Ylb, double Xrt, double Yrt, 95 | double[] X, double[] Y) { 96 | int i; 97 | double XDelt, YDelt; 98 | int Xnum = X.length; 99 | int Ynum = Y.length; 100 | XDelt = (Xrt - Xlb) / Xnum; 101 | YDelt = (Yrt - Ylb) / Ynum; 102 | for (i = 0; i < Xnum; i++) { 103 | X[i] = Xlb + i * XDelt; 104 | } 105 | for (i = 0; i < Ynum; i++) { 106 | Y[i] = Ylb + i * YDelt; 107 | } 108 | } 109 | 110 | 111 | 112 | 113 | 114 | 115 | } -------------------------------------------------------------------------------- /src/test/java/contour/kriging/KrigingTest.java: -------------------------------------------------------------------------------- 1 | package contour.kriging; 2 | 3 | import contour.bean.Tuple5; 4 | import contour.utils.ColorUtils; 5 | import contour.utils.CsvParser; 6 | import contour.utils.FileUtils; 7 | import org.junit.Test; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.awt.*; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * KrigingTest 18 | */ 19 | public class KrigingTest { 20 | private Logger logger = LoggerFactory.getLogger("KrigingTest"); 21 | 22 | @Test 23 | public void testBuildContourImage() { 24 | int zoom = 9; 25 | String inputPath = "input/"; 26 | String fileName = "data.csv"; 27 | String outputPath = FileUtils.getDiskRootPath() + "/Tmp/"; 28 | String borderDataPath = "input/border.csv"; 29 | double[][] bounds = new double[][]{{116.760922, 23.391427}, {118.364926, 25.402349}}; 30 | List> colors = buildAQIColors(); 31 | double[][] rawdata = getData(inputPath, fileName); 32 | long time = System.currentTimeMillis(); 33 | KrigingImage krigingImage = new KrigingImage(rawdata, colors, bounds, outputPath + "krigin_contour", borderDataPath, zoom); 34 | logger.info("Interpolation cost time: "+ (System.currentTimeMillis() - time)); 35 | krigingImage.setClipBounds(false); 36 | krigingImage.draw(); 37 | } 38 | 39 | public List> buildAQIColors() { 40 | List> retList = new ArrayList<>(); 41 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(0,228,0), new Color(255,255,9), new int[]{0, 50}, 10)); 42 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(255,255,9), new Color(255,126,0), new int[]{50, 100}, 15)); 43 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(255,126,0), new Color(255,0,0), new int[]{100, 150}, 20)); 44 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(255,0,0), new Color(153,0,76), new int[]{150, 200}, 20)); 45 | retList.addAll(ColorUtils.buildInterpolationColors( new Color(153,0,76), new Color(126,0,35), new int[]{200, 300}, 20)); 46 | retList.add(new Tuple5(300D, 3000D, 126, 0, 35)); 47 | return retList; 48 | } 49 | 50 | public double[][] getData(String path, String fileName){ 51 | String dataPath = this.getClass().getClassLoader() 52 | .getResource(path+"/"+fileName).getPath(); 53 | List> dataList = CsvParser.parse(dataPath); 54 | List list = new ArrayList<>(); 55 | for (int i=0; i map = dataList.get(i); 57 | if("null".equals(map.get("VALUE"))) { 58 | continue; 59 | } 60 | Double lon = Double.parseDouble(map.get("LON").trim()); 61 | Double lat = Double.parseDouble(map.get("LAT").trim()); 62 | Double value = Double.parseDouble(map.get("VALUE").trim()); 63 | list.add(new double[]{lon,lat,value}); 64 | } 65 | int len=list.size(); 66 | double[][] retList = new double[len][3]; 67 | for(int i=0; i> buildInterpolationColors(Color startColor, 63 | Color endColor, int[] threshold, int interval) { 64 | InterpolateRgb interpolate = new InterpolateRgb(startColor, endColor); 65 | int min = threshold[0]; 66 | int max = threshold[1]; 67 | List> retList = new ArrayList<>(); 68 | int delta = max -min; 69 | for(int i=0; i(value_min, value_max, r, g, b)); 77 | } 78 | return retList; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/contour/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package contour.utils; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public class FileUtils { 7 | public static String getDiskRootPath() { 8 | String currentPath = System.getProperty("user.dir"); 9 | return currentPath.substring(0, currentPath.indexOf("\\")); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/contour/utils/InterpolateRgb.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created: 2020-08-08 09:50:28 3 | * Author : xuwei 4 | * Email : xuw@ahtsoft.com 5 | * ----- 6 | * Description: 线性颜色插值类 7 | */ 8 | 9 | package contour.utils; 10 | 11 | import java.awt.Color; 12 | 13 | public class InterpolateRgb { 14 | private Color startRgb; 15 | private int deltR; 16 | private int deltG; 17 | private int deltB; 18 | private int deltA; 19 | 20 | /** 21 | * 22 | * @param startRgb rgb颜色下限 23 | * @param endRgb rgb颜色上限 24 | */ 25 | public InterpolateRgb(Color startRgb, Color endRgb) { 26 | this.startRgb = startRgb; 27 | this.deltR = endRgb.getRed() - startRgb.getRed(); 28 | this.deltG = endRgb.getGreen() - startRgb.getGreen(); 29 | this.deltB = endRgb.getBlue() - startRgb.getBlue(); 30 | this.deltA = endRgb.getAlpha() - startRgb.getAlpha(); 31 | } 32 | 33 | private int linearR(float t) { 34 | return (int) (startRgb.getRed() + t * this.deltR); 35 | } 36 | 37 | private int linearG(float t) { 38 | return (int) (startRgb.getGreen() + t * this.deltG); 39 | } 40 | 41 | private int linearB(float t) { 42 | return (int) (startRgb.getBlue() + t * this.deltB); 43 | } 44 | 45 | private float linearA(float t) { 46 | return startRgb.getAlpha() + t * this.deltA; 47 | } 48 | 49 | /** 50 | * 51 | * @param ratio 位于[0,1]区间 52 | * @return 53 | */ 54 | public Color get(float ratio) { 55 | int r = linearR(ratio); 56 | int g = linearG(ratio); 57 | int b = linearB(ratio); 58 | if (deltA != 0) { 59 | float a = linearA(ratio); 60 | return new Color(r, g, b, a); 61 | } else { 62 | return new Color(r, g, b); 63 | } 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/test/java/contour/utils/MapUtils.java: -------------------------------------------------------------------------------- 1 | package contour.utils; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import contour.draw.spatial.PointD; 9 | 10 | /** 11 | * 读取地图数据 12 | */ 13 | public class MapUtils { 14 | 15 | public static List> readMapData(String filePath){ 16 | List> _clipLines = null; 17 | 18 | try { 19 | String borderPath = MapUtils.class.getClassLoader().getResource(filePath).getPath(); 20 | List> borderList = CsvParser.parse(borderPath); 21 | _clipLines = parseMapData(borderList); 22 | } catch (IOException ex) { 23 | ex.printStackTrace(); 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | 28 | return _clipLines; 29 | } 30 | 31 | /* 解析地图数据 32 | * @param list 33 | * @return 34 | * @throws Exception 35 | */ 36 | private static List> parseMapData(List> list) throws Exception { 37 | List> clipLines = new ArrayList<>(); 38 | for(int i=0, size=list.size(); i dataMap = list.get(i); 40 | String[] spots = dataMap.get("REGION").split(","); 41 | List spotPoints = new ArrayList<>(); 42 | for(String s: spots){ 43 | PointD aPoint = new PointD(); 44 | String horizontal = s.split("\\s+")[0]; 45 | String vertical = s.split("\\s+")[1]; 46 | aPoint.X = Double.valueOf(horizontal); 47 | aPoint.Y = Double.valueOf(vertical); 48 | spotPoints.add(aPoint); 49 | } 50 | clipLines.add(spotPoints); 51 | } 52 | return clipLines; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/contour/utils/PakoGzipUtils.java: -------------------------------------------------------------------------------- 1 | package contour.utils; 2 | import java.io.ByteArrayOutputStream; 3 | import java.io.IOException; 4 | import java.util.zip.GZIPOutputStream; 5 | 6 | 7 | public class PakoGzipUtils { 8 | 9 | /** 10 | * @param str:正常的字符串 11 | * @return 压缩字符串 类型为: ³)°K,NIc i£_`Çe# c¦%ÂXHòjyIÅÖ` 12 | * @throws IOException 13 | */ 14 | public static String compress(String str) throws IOException { 15 | if (str == null || str.length() == 0) { 16 | return str; 17 | } 18 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 19 | GZIPOutputStream gzip = new GZIPOutputStream(out); 20 | gzip.write(str.getBytes()); 21 | gzip.close(); 22 | return out.toString("ISO-8859-1"); 23 | } 24 | 25 | 26 | } -------------------------------------------------------------------------------- /src/test/java/graphics/GraphicsTest.java: -------------------------------------------------------------------------------- 1 | package graphics; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.image.BufferedImage; 6 | import java.awt.*; 7 | import java.io.File; 8 | 9 | import javax.imageio.ImageIO; 10 | 11 | import org.junit.Test; 12 | 13 | /** 14 | * GraphicsTest 15 | */ 16 | public class GraphicsTest { 17 | 18 | 19 | public void test1() { 20 | int width = 200, height = 250; 21 | //创建图片对象 22 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); 23 | //基于图片对象打开绘图 24 | Graphics2D graphics = image.createGraphics(); 25 | //绘图逻辑 START (基于业务逻辑进行绘图处理)…… 26 | 27 | //绘制圆形 28 | graphics.setColor(Color.BLACK); 29 | // Ellipse2D.Double ellipse = new Ellipse2D.Double(20, 20, 100, 100); 30 | // graphics.draw(ellipse); 31 | 32 | // 绘图逻辑 END 33 | //处理绘图 34 | graphics.dispose(); 35 | //将绘制好的图片写入到图片 36 | // ImageIO.write(image, "png", new File("abc.png"));w 37 | 38 | } 39 | 40 | 41 | @Test 42 | public void testCreateMask() { 43 | int width = 1536; 44 | int height = 731; 45 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR); 46 | Graphics2D graphics = image.createGraphics(); 47 | graphics.setColor(new Color(255,255,255, 0)); 48 | 49 | } 50 | 51 | 52 | } -------------------------------------------------------------------------------- /src/test/resources/input/border.csv: -------------------------------------------------------------------------------- 1 | REGION 2 | "118.081141 24.361268,118.081235 24.362018,118.081352 24.362282,118.081557 24.362399,118.081757 24.362423,118.082073 24.362388,118.082501 24.362189,118.082782 24.362055,118.083046 24.362002,118.083258 24.362008,118.083551 24.362067,118.083896 24.362232,118.084114 24.362448,118.084518 24.362917,118.084881 24.363392,118.085198 24.363545,118.085485 24.363662,118.08582 24.363639,118.085989 24.363604,118.086101 24.363458,118.086177 24.36327,118.086194 24.363024,118.08606 24.362778,118.085984 24.362626,118.085843 24.362303,118.085643 24.361981,118.085427 24.361893,118.085162 24.361869,118.084829 24.361705,118.084635 24.361465,118.084594 24.361271,118.084576 24.361031,118.084518 24.360861,118.084248 24.360744,118.084113 24.360744,118.083838 24.360614,118.083674 24.36038,118.083504 24.360064,118.083287 24.359859,118.082906 24.359723,118.082589 24.359752,118.082472 24.359852,118.082302 24.360074,118.082132 24.36018,118.081956 24.360203,118.081868 24.36032,118.081692 24.360361,118.081475 24.360361,118.081288 24.360437,118.081135 24.360694,118.081141 24.361268" 3 | "117.730669 23.790997,117.731264 23.790907,117.731459 23.790609,117.731665 23.7899,117.731619 23.789568,117.73131 23.789294,117.731093 23.788961,117.731104 23.788527,117.731138 23.788264,117.73131 23.787932,117.73155 23.787715,117.731647 23.787436,117.731481 23.786845,117.731161 23.78601,117.730554 23.785551,117.729809 23.785551,117.729088 23.785813,117.728412 23.786179,117.728068 23.786396,117.727862 23.786887,117.727347 23.787482,117.726557 23.787824,117.72603 23.787813,117.725583 23.787606,117.724664 23.787584,117.723977 23.787566,117.723647 23.787821,117.723693 23.788153,117.723853 23.788336,117.723849 23.78849,117.723303 23.788714,117.722925 23.789239,117.722994 23.789651,117.723246 23.790052,117.723281 23.790464,117.723521 23.790842,117.723888 23.790945,117.724231 23.790957,117.724667 23.790591,117.725091 23.79042,117.725491 23.790272,117.725732 23.790077,117.726018 23.789906,117.726351 23.789884,117.726511 23.79001,117.726717 23.790044,117.727061 23.789907,117.727381 23.78977,117.727874 23.789816,117.728183 23.789954,117.728504 23.790378,117.728699 23.790641,117.729168 23.790893,117.730669 23.790997" 4 | "117.711567 23.77274,117.710336 23.77352,117.709597 23.774726,117.708814 23.776468,117.708299 23.777562,117.708031 23.778567,117.708187 23.779215,117.708613 23.779306,117.709083 23.779083,117.710135 23.778302,117.710627 23.777744,117.710963 23.777588,117.711232 23.7777,117.711523 23.778014,117.71159 23.77855,117.711747 23.778796,117.712015 23.778864,117.712463 23.778752,117.712888 23.778262,117.713246 23.778173,117.713784 23.778151,117.714298 23.778398,117.71488 23.778376,117.715305 23.778109,117.715731 23.777327,117.716245 23.777037,117.717342 23.777173,117.718393 23.77771,117.719333 23.778024,117.720049 23.777958,117.721079 23.777199,117.721661 23.777357,117.721907 23.777335,117.72231 23.777044,117.722332 23.776598,117.722668 23.776107,117.723339 23.775928,117.723876 23.776041,117.724346 23.775929,117.72486 23.775506,117.725688 23.774456,117.725733 23.77392,117.725576 23.773138,117.725397 23.772646,117.725397 23.771931,117.725665 23.771596,117.726225 23.771216,117.726381 23.770814,117.726269 23.770568,117.726023 23.770412,117.725397 23.770366,117.724659 23.770589,117.724121 23.771125,117.723562 23.771459,117.722443 23.77157,117.722018 23.771927,117.721549 23.772664,117.720967 23.772998,117.720228 23.773422,117.719803 23.773488,117.719288 23.772996,117.719087 23.772281,117.719109 23.771856,117.719087 23.771387,117.718572 23.770582,117.717901 23.769821,117.717587 23.76962,117.716446 23.769484,117.715126 23.769147,117.713917 23.768788,117.713223 23.768787,117.712484 23.769367,117.712373 23.770261,117.712731 23.770887,117.712843 23.771289,117.712821 23.771892,117.712306 23.772451,117.711567 23.77274" 5 | "117.683735 23.784611,117.684136 23.784297,117.68448 23.783697,117.684824 23.783641,117.685125 23.783756,117.685541 23.783728,117.685828 23.783542,117.686659 23.783101,117.68739 23.783173,117.687992 23.783475,117.688293 23.784062,117.688751 23.784764,117.688952 23.784893,117.689153 23.784893,117.689511 23.784851,117.689611 23.784679,117.689683 23.784307,117.689998 23.784036,117.69027 23.783937,117.690428 23.783594,117.690385 23.783264,117.690385 23.782879,117.690672 23.782379,117.690915 23.781821,117.690972 23.781278,117.690958 23.780749,117.691058 23.780119,117.691187 23.779576,117.691445 23.779277,117.691775 23.77932,117.692148 23.779221,117.692363 23.778935,117.692907 23.778321,117.693495 23.777464,117.693509 23.777063,117.693437 23.77662,117.693509 23.776363,117.693667 23.776063,117.693681 23.775776,117.693552 23.775261,117.693222 23.775003,117.692835 23.774931,117.692047 23.775202,117.691087 23.775443,117.690471 23.775556,117.689954 23.775656,117.689468 23.775926,117.689166 23.776283,117.689138 23.776812,117.689166 23.777199,117.688994 23.777656,117.688608 23.777984,117.688579 23.77847,117.688722 23.778928,117.688464 23.779171,117.688149 23.779299,117.68749 23.779298,117.686931 23.779569,117.685985 23.78021,117.685627 23.780796,117.685426 23.781053,117.685054 23.781195,117.684652 23.781166,117.683434 23.780921,117.68309 23.780992,117.682817 23.781177,117.682731 23.781549,117.682789 23.781763,117.683047 23.782036,117.683305 23.782608,117.683363 23.783009,117.683305 23.783309,117.683076 23.783723,117.682961 23.784166,117.68299 23.784567,117.683176 23.784696,117.683406 23.784725,117.683735 23.784611" 6 | "118.151146 24.318373,118.150022 24.31848,118.14895 24.31932,118.148977 24.320996,118.148768 24.321598,118.149107 24.322226,118.148794 24.322751,118.147212 24.323798,118.147045 24.324427,118.147129 24.324573,118.148112 24.325263,118.14807 24.325493,118.146481 24.327968,118.146418 24.328219,118.146606 24.328827,118.146794 24.328994,118.147778 24.329454,118.148217 24.329956,118.148112 24.330982,118.146857 24.333058,118.146439 24.333436,118.14577 24.333437,118.145687 24.333626,118.145812 24.334149,118.145352 24.335323,118.144955 24.336455,118.144934 24.337063,118.145185 24.338131,118.145531 24.338635,118.146282 24.339125,118.148406 24.338893,118.149778 24.338039,118.150922 24.336957,118.152359 24.3363,118.153339 24.335873,118.154254 24.334758,118.154496 24.334022,118.15466 24.33283,118.155886 24.332103,118.156332 24.331549,118.156423 24.330874,118.156219 24.330319,118.15583 24.329826,118.15491 24.329444,118.154092 24.329107,118.153577 24.329211,118.152819 24.328846,118.152532 24.326437,118.152558 24.326149,118.152897 24.325729,118.153394 24.325231,118.153655 24.324759,118.15389 24.324471,118.154884 24.325019,118.15504 24.324861,118.154674 24.323893,118.154936 24.323264,118.155458 24.323106,118.156216 24.322267,118.156373 24.321481,118.155458 24.321168,118.155249 24.320252,118.154387 24.319442,118.153158 24.31926,118.15206 24.319812,118.151721 24.319551,118.151146 24.318373" 7 | "118.049015 24.418307,118.050989 24.41807,118.086316 24.410304,118.088079 24.408986,118.088513 24.407927,118.088608 24.406168,118.086932 24.397052,118.084698 24.38489,118.085183 24.376546,118.083562 24.370955,118.080156 24.36674,118.076182 24.363657,118.075695 24.360902,118.07737 24.359428,118.078644 24.357244,118.081379 24.356279,118.08672 24.357828,118.096454 24.358466,118.103308 24.358073,118.106657 24.358072,118.112112 24.356925,118.116154 24.353842,118.119803 24.350185,118.120948 24.348644,118.121346 24.347279,118.121961 24.337726,118.122403 24.336623,118.122932 24.33636,118.124553 24.335873,118.125697 24.335299,118.126581 24.334155,118.126841 24.333163,118.126312 24.321101,118.126493 24.319253,118.127109 24.318458,118.139587 24.314704,118.143901 24.311263,118.145304 24.308443,118.145659 24.306329,118.145217 24.303952,118.144247 24.300432,118.1439 24.296999,118.144419 24.293829,118.146091 24.291448,118.150222 24.287435,118.154094 24.283556,118.157427 24.277914,118.158397 24.269988,118.157426 24.26365,118.155495 24.258723,118.153208 24.255822,118.149778 24.253539,118.14679 24.252311,118.143455 24.251787,118.12596 24.252513,118.124816 24.251985,118.124374 24.251237,118.123844 24.235917,118.122084 24.232927,118.119622 24.230992,118.115754 24.229408,118.107124 24.228179,118.095332 24.227649,118.074716 24.225432,118.068538 24.223135,118.0636 24.219252,118.059722 24.214491,118.056723 24.209378,118.053539 24.206202,118.04913 24.203374,118.043663 24.201199,118.03889 24.1993,118.031824 24.197346,118.025819 24.196624,118.019422 24.197244,118.016419 24.192304,118.001401 24.176852,118.000696 24.175705,118.000566 24.173746,118.000382 24.156488,118.000033 24.152437,117.995611 24.147142,117.99084 24.142726,117.974773 24.134494,117.963114 24.125835,117.947234 24.111364,117.936038 24.099813,117.931624 24.094439,117.928453 24.089329,117.927219 24.086334,117.925012 24.07515,117.924926 24.072466,117.925811 24.067007,117.928095 24.056269,117.928806 24.046057,117.927564 24.039894,117.923864 24.033552,117.918054 24.025008,117.915058 24.017436,117.910649 24.011799,117.899907 24.006517,117.890055 24.004761,117.864905 24.004789,117.859985 24.001979,117.847685 23.984746,117.826069 23.966384,117.816048 23.95619,117.807612 23.947402,117.80198 23.936844,117.798115 23.924613,117.792837 23.906307,117.787915 23.899974,117.778769 23.893996,117.769265 23.888369,117.762577 23.886962,117.728042 23.888006,117.704064 23.890087,117.691004 23.889008,117.679002 23.884057,117.67194 23.878056,117.658528 23.851793,117.651647 23.81533,117.652351 23.81093,117.654646 23.806705,117.657029 23.804069,117.659316 23.801431,117.660463 23.799144,117.661437 23.796944,117.661611 23.794128,117.660993 23.790958,117.660289 23.789106,117.658263 23.786638,117.654288 23.783196,117.639464 23.767765,117.633866 23.758863,117.627652 23.749392,117.624611 23.742674,117.61297 23.713388,117.60769 23.70546,117.601517 23.701938,117.595356 23.701234,117.589899 23.701853,117.579779 23.706352,117.568614 23.71253,117.544708 23.715836,117.540752 23.715316,117.53715 23.714401,117.532234 23.71195,117.523934 23.706039,117.522835 23.705339,117.521606 23.704726,117.520334 23.704333,117.518759 23.704205,117.502647 23.704618,117.501592 23.704224,117.500277 23.703261,117.497109 23.697722,117.493077 23.690249,117.488862 23.679495,117.48842 23.674654,117.488947 23.67289,117.490003 23.671744,117.493863 23.670762,117.496934 23.669169,117.498604 23.667316,117.501416 23.659822,117.501848 23.656077,117.50132 23.650531,117.499477 23.647366,117.496586 23.644645,117.493073 23.642454,117.486487 23.640711,117.47656 23.63986,117.466544 23.639886,117.463565 23.639012,117.461366 23.637739,117.457494 23.633345,117.454687 23.628242,117.454158 23.62296,117.453984 23.616796,117.45433 23.612039,117.455733 23.606401,117.460834 23.601633,117.46338 23.598546,117.464177 23.593436,117.464081 23.587534,117.463734 23.585202,117.458445 23.579912,117.456656 23.577269,117.454016 23.575056,117.449842 23.573187,117.441581 23.570713,117.43478 23.570608,117.427533 23.572151,117.422672 23.571216,117.410502 23.565055,117.387684 23.555274,117.3824 23.553509,117.375689 23.552796,117.371281 23.55332,117.366516 23.554722,117.361749 23.556299,117.356635 23.55629,117.349214 23.555221,117.333327 23.551139,117.317787 23.548996,117.311079 23.548984,117.302607 23.550381,117.298024 23.553721,117.294667 23.559,117.292728 23.565162,117.291259 23.571187,117.275176 23.566598,117.271466 23.566155,117.192939 23.561634,117.192197 23.61855,117.192241 23.619074,117.191807 23.62174,117.19179 23.624602,117.192198 23.627084,117.192588 23.629574,117.192172 23.630334,117.191548 23.631281,117.190924 23.632233,117.190499 23.633182,117.189884 23.633754,117.189251 23.634514,117.187588 23.635468,117.185924 23.636222,117.1853 23.636223,117.184676 23.636215,117.183644 23.63564,117.182821 23.635444,117.180533 23.635622,117.178461 23.635999,117.177629 23.635613,117.177222 23.635424,117.175983 23.635032,117.174735 23.634837,117.173704 23.634641,117.173079 23.635022,117.172031 23.635972,117.171399 23.637113,117.170247 23.637995,117.166949 23.639101,117.166081 23.639131,117.163936 23.638417,117.162272 23.638792,117.15936 23.640116,117.156864 23.642011,117.154151 23.644673,117.152062 23.647719,117.149973 23.65,117.148299 23.652659,117.147875 23.653228,117.147459 23.653799,117.147042 23.653609,117.146219 23.653222,117.145603 23.653025,117.144355 23.651884,117.143115 23.651109,117.142291 23.650913,117.141875 23.651104,117.14125 23.651863,117.141034 23.653008,117.140618 23.653009,117.140201 23.653003,117.139369 23.653004,117.138744 23.652998,117.137921 23.652231,117.137097 23.651655,117.135848 23.651459,117.134608 23.651454,117.133576 23.651447,117.132951 23.651439,117.131702 23.650863,117.130878 23.650287,117.128405 23.648369,117.125924 23.646632,117.124883 23.646053,117.124258 23.646245,117.123634 23.647193,117.123625 23.648719,117.1232 23.649676,117.122992 23.651204,117.122983 23.651584,117.122558 23.653298,117.121924 23.655006,117.118972 23.658044,117.1171 23.662612,117.115425 23.665278,117.114384 23.665649,117.113342 23.665841,117.111675 23.666015,117.110017 23.666198,117.1069 23.665992,117.103991 23.665778,117.101298 23.665572,117.098806 23.665745,117.097546 23.666313,117.096504 23.66745,117.095461 23.668209,117.09441 23.669148,117.093785 23.669148,117.093376 23.669148,117.091299 23.668563,117.089422 23.668544,117.087962 23.669303,117.086919 23.670819,117.086276 23.672335,117.085642 23.674051,117.083764 23.675754,117.082087 23.677651,117.079774 23.680494,117.078096 23.683731,117.077036 23.685817,117.076401 23.687911,117.075349 23.689434,117.074088 23.690952,117.073262 23.691131,117.072427 23.691321,117.071592 23.691311,117.070966 23.69131,117.070139 23.69092,117.069513 23.690531,117.068478 23.689564,117.067451 23.688985,117.066616 23.688786,117.065989 23.688976,117.064319 23.689533,117.063057 23.690479,117.060351 23.691216,117.057845 23.69234,117.055538 23.694229,117.053859 23.696702,117.053223 23.698406,117.053206 23.700319,117.052771 23.702985,117.052963 23.705662,117.053363 23.707002,117.05399 23.708529,117.053355 23.709294,117.052728 23.710051,117.051675 23.711186,117.051248 23.712513,117.05103 23.713278,117.051448 23.714238,117.051849 23.715194,117.052267 23.716342,117.052258 23.717297,117.052249 23.718245,117.051405 23.719191,117.050778 23.720338,117.049943 23.721664,117.049516 23.723377,117.049299 23.724144,117.049499 23.725093,117.050117 23.72606,117.050944 23.727207,117.050936 23.727776,117.050718 23.728732,117.050091 23.729679,117.049883 23.730824,117.049874 23.731781,117.050274 23.732929,117.050692 23.733507,117.051101 23.734465,117.051093 23.735413,117.051093 23.736749,117.050667 23.737506,117.050449 23.738462,117.049387 23.741701,117.048883 23.753524,117.047813 23.758668,117.046977 23.759614,117.045087 23.761315,117.043824 23.762071,117.042989 23.763208,117.042562 23.763775,117.042562 23.764732,117.042127 23.766825,117.041909 23.768918,117.041274 23.770253,117.040847 23.771777,117.040203 23.77368,117.039776 23.775963,117.039341 23.777099,117.039332 23.778436,117.038278 23.780338,117.036389 23.782806,117.035753 23.784132,117.035326 23.785656,117.035109 23.786603,117.0349 23.787559,117.034473 23.788316,117.034046 23.789082,117.032792 23.790208,117.031747 23.791341,117.029221 23.793042,117.026921 23.794929,117.024186 23.796997,117.021668 23.799266,117.020056 23.800724,117.018976 23.802066,117.018828 23.802625,117.018828 23.803059,117.018976 23.803662,117.019255 23.804574,117.019543 23.805523,117.01888 23.807608,117.018471 23.808779,117.018253 23.809917,117.018036 23.811062,117.018454 23.812208,117.02031 23.813947,117.022593 23.815876,117.024884 23.817804,117.026748 23.819923,117.026531 23.820688,117.026322 23.821636,117.025686 23.82297,117.02525 23.824494,117.024614 23.826009,117.024188 23.827721,117.022672 23.835915,117.022655 23.837249,117.022446 23.838774,117.021183 23.840289,117.019711 23.841801,117.018656 23.842943,117.018021 23.844269,117.017803 23.845604,117.017785 23.847127,117.017367 23.847697,117.016949 23.84845,117.015895 23.848827,117.014858 23.849203,117.013804 23.849762,117.013377 23.850716,117.013377 23.851286,117.013577 23.852052,117.013577 23.852812,117.013569 23.853766,117.012933 23.854524,117.012096 23.85528,117.008959 23.856013,116.999714 23.858979,116.995349 23.861862,116.99509 23.862029,116.991476 23.862796,116.989931 23.862775,116.986513 23.861901,116.984638 23.861276,116.982984 23.858938,116.981551 23.855596,116.973503 23.855668,116.965789 23.858482,116.962044 23.861401,116.959619 23.866241,116.960059 23.86816,116.962261 23.869982,116.964795 23.870797,116.96766 23.870804,116.970635 23.870409,116.977017 23.869938,116.975485 23.87227,116.975705 23.8752,116.977137 23.879751,116.977534 23.880781,116.979987 23.881846,116.980445 23.88285,116.979271 23.884455,116.9746 23.885074,116.973497 23.886478,116.973166 23.888191,116.974048 23.889607,116.974762 23.891394,116.973021 23.894813,116.972254 23.895425,116.97096 23.896053,116.967103 23.896952,116.962694 23.898756,116.958838 23.900664,116.958066 23.902278,116.957955 23.903289,116.958947 23.904805,116.959938 23.905412,116.960599 23.907939,116.960267 23.911573,116.959274 23.914497,116.957621 23.916009,116.956299 23.916813,116.954976 23.918827,116.954755 23.920442,116.955856 23.922264,116.959162 23.923484,116.962334 23.922597,116.967159 23.923144,116.971726 23.926243,116.973329 23.927062,116.973374 23.927157,116.974846 23.929247,116.976545 23.931526,116.976763 23.933053,116.97678 23.934958,116.976798 23.936292,116.977433 23.937621,116.978705 23.939141,116.980178 23.941041,116.980404 23.942367,116.980622 23.943893,116.98064 23.945995,116.980666 23.94809,116.979848 23.950381,116.979447 23.952671,116.979874 23.95362,116.980719 23.95457,116.981991 23.956849,116.982636 23.959322,116.979961 23.963341,116.976443 23.967366,116.975406 23.968129,116.974788 23.969272,116.973551 23.971571,116.973159 23.97424,116.973377 23.974809,116.974004 23.97557,116.974222 23.976907,116.974449 23.978621,116.973839 23.980333,116.973438 23.982057,116.973665 23.984153,116.974527 23.986437,116.974745 23.988533,116.975399 23.990619,116.97688 23.992328,116.978779 23.994229,116.980042 23.995361,116.981305 23.99669,116.981811 23.999435,116.979319 24.002678,116.977029 24.004394,116.974529 24.005921,116.972229 24.00688,116.96993 24.007452,116.968676 24.007268,116.967631 24.006886,116.966377 24.006315,116.965532 24.005744,116.964279 24.004802,116.963434 24.003852,116.96238 24.002902,116.961327 24.002329,116.9607 24.002139,116.960073 24.002137,116.959446 24.002335,116.959037 24.002714,116.957366 24.003673,116.955912 24.004816,116.954449 24.00596,116.953622 24.00749,116.953214 24.008437,116.953222 24.009583,116.953231 24.010721,116.953658 24.011868,116.954076 24.013005,116.954711 24.014343,116.955138 24.015483,116.955773 24.016818,116.955773 24.017577,116.955782 24.018525,116.955582 24.01929,116.955164 24.020246,116.95392 24.0212,116.952875 24.022154,116.951422 24.023108,116.950386 24.024063,116.949759 24.02445,116.949342 24.025018,116.949142 24.025783,116.948724 24.026351,116.947897 24.027115,116.947062 24.027881,116.946236 24.028648,116.9454 24.029221,116.944565 24.02922,116.943938 24.02903,116.943094 24.028849,116.942686 24.029037,116.941642 24.029612,116.941016 24.030368,116.940398 24.031324,116.940198 24.032469,116.93978 24.033047,116.939789 24.033804,116.939998 24.034562,116.940424 24.035133,116.941051 24.035514,116.942095 24.036084,116.943138 24.036276,116.9444 24.036647,116.945026 24.036648,116.945862 24.036641,116.946697 24.036644,116.947533 24.037025,116.948168 24.037406,116.948794 24.037974,116.949212 24.038734,116.949639 24.039494,116.950065 24.040831,116.950283 24.042345,116.950501 24.043871,116.950718 24.045587,116.950309 24.046922,116.950109 24.048445,116.9497 24.04978,116.949918 24.051108,116.950127 24.051299,116.950753 24.051489,116.951171 24.05149,116.951798 24.051869,116.952016 24.05244,116.952233 24.053197,116.952442 24.053957,116.952442 24.054723,116.952033 24.054913,116.951824 24.055101,116.951615 24.0553,116.95078 24.055485,116.950153 24.055484,116.949318 24.055682,116.948691 24.055681,116.947647 24.055499,116.946603 24.055117,116.945759 24.054925,116.944923 24.054744,116.943253 24.05417,116.941574 24.053608,116.940104 24.053426,116.938651 24.053424,116.938024 24.05362,116.937398 24.053999,116.936981 24.054575,116.936772 24.055332,116.936372 24.056667,116.935955 24.058001,116.935755 24.059526,116.935346 24.060862,116.934511 24.061438,116.933676 24.062014,116.93285 24.062581,116.932024 24.063346,116.93119 24.063924,116.930355 24.064689,116.929738 24.065454,116.929529 24.066401,116.92932 24.06736,116.929547 24.068495,116.929755 24.069643,116.929973 24.070968,116.929773 24.072493,116.929573 24.074018,116.929382 24.075544,116.929182 24.077258,116.928765 24.079171,116.928565 24.081073,116.928374 24.082977,116.928382 24.085071,116.928609 24.086598,116.928817 24.088115,116.929252 24.08964,116.929461 24.091354,116.929061 24.09231,116.928644 24.093258,116.928235 24.094214,116.927818 24.095359,116.927192 24.096315,116.926992 24.097452,116.926584 24.098597,116.926593 24.099933,116.926601 24.100692,116.92681 24.101649,116.927027 24.102595,116.927445 24.103554,116.927662 24.104311,116.928297 24.105071,116.928714 24.105829,116.929349 24.106779,116.929558 24.107357,116.929984 24.107926,116.930401 24.108685,116.930828 24.109443,116.932315 24.11439,116.934454 24.12238,116.934898 24.126949,116.935316 24.127327,116.935942 24.127898,116.936586 24.128467,116.937212 24.129039,116.937838 24.129417,116.938882 24.129798,116.939517 24.13037,116.940361 24.130937,116.940561 24.131508,116.94057 24.132076,116.94057 24.132842,116.941005 24.133411,116.941414 24.133791,116.942257 24.133975,116.943093 24.134354,116.943928 24.134546,116.944554 24.134547,116.945181 24.134539,116.945808 24.134731,116.946652 24.134921,116.947069 24.134914,116.947278 24.135103,116.947913 24.135672,116.948331 24.136053,116.948958 24.136813,116.949811 24.138339,116.950446 24.140048,116.951299 24.141763,116.952352 24.143473,116.953188 24.144043,116.954241 24.144613,116.955286 24.144995,116.956339 24.145747,116.957392 24.146886,116.958655 24.148226,116.959709 24.149553,116.960971 24.150884,116.961598 24.151644,116.96266 24.152215,116.963487 24.152777,116.964759 24.153348,116.966431 24.153346,116.968311 24.153339,116.970193 24.153336,116.972292 24.153523,116.972919 24.153525,116.973546 24.153906,116.974173 24.154287,116.974809 24.154857,116.975445 24.155996,116.976081 24.157323,116.976507 24.15866,116.977353 24.15999,116.977989 24.16094,116.978834 24.162079,116.979888 24.16303,116.981143 24.16379,116.982406 24.164174,116.983878 24.164549,116.985341 24.164733,116.986814 24.165306,116.98779 24.16584,116.987859 24.165877,116.988704 24.166439,116.989541 24.167389,116.990386 24.168351,116.990813 24.1693,116.99124 24.170627,116.991667 24.171773,116.992303 24.172912,116.992731 24.173484,116.993567 24.173864,116.994203 24.174436,116.995039 24.175007,116.996094 24.175758,116.997148 24.17671,116.997994 24.177857,116.99863 24.178996,116.998639 24.180332,116.99822 24.181856,116.997611 24.183379,116.99511 24.186621,116.993237 24.188341,116.991154 24.189866,116.989029 24.191042,116.985561 24.192414,116.982242 24.194268,116.980047 24.195982,116.978914 24.197844,116.977808 24.199034,116.976763 24.199606,116.976345 24.199796,116.97361 24.198659,116.972356 24.198278,116.971311 24.198093,116.970683 24.19828,116.970056 24.198856,116.969639 24.199625,116.969229 24.200379,116.968184 24.201335,116.967348 24.202287,116.966521 24.203242,116.965894 24.204386,116.965485 24.205153,116.965285 24.2061,116.964875 24.207056,116.964666 24.208012,116.963831 24.208587,116.962995 24.209342,116.962159 24.209919,116.961332 24.210494,116.960078 24.211066,116.958824 24.211643,116.95778 24.212218,116.956953 24.213172,116.956544 24.214128,116.956544 24.215074,116.956344 24.21603,116.956135 24.216978,116.955299 24.217174,116.954255 24.217371,116.953202 24.217178,116.952166 24.217187,116.950277 24.216992,116.948598 24.216807,116.946927 24.216815,116.946074 24.217039,116.945909 24.217039,116.944473 24.216472,116.943246 24.216281,116.940776 24.217215,116.937496 24.217961,116.935235 24.218708,116.934009 24.219269,116.9336 24.220019,116.933391 24.222272,116.933183 24.224899,116.933383 24.226402,116.934001 24.228093,116.934618 24.229595,116.93514 24.231032,116.935331 24.231032,116.936166 24.230836,116.936793 24.230837,116.937636 24.230829,116.937836 24.23102,116.938054 24.231398,116.938054 24.231786,116.938271 24.232357,116.938489 24.234072,116.938706 24.235777,116.938924 24.237682,116.939151 24.239587,116.938942 24.240355,116.938733 24.2413,116.938533 24.242068,116.938333 24.243024,116.937707 24.243971,116.936881 24.244925,116.936254 24.246071,116.935846 24.247216,116.935428 24.247984,116.935228 24.248929,116.935237 24.249886,116.935446 24.250834,116.935663 24.251791,116.936081 24.252929,116.936507 24.253877,116.936933 24.255025,116.936733 24.255782,116.936734 24.25655,116.936525 24.257307,116.936534 24.258264,116.936334 24.260548,116.936569 24.263021,116.936369 24.265503,116.936369 24.266043,116.936873 24.267104,116.937256 24.267749,116.938935 24.268496,116.939204 24.268943,116.939266 24.26949,116.938987 24.271325,116.938822 24.272019,116.939318 24.27321,116.939474 24.273855,116.939422 24.274549,116.939092 24.275095,116.938335 24.275342,116.937091 24.27509,116.936274 24.275189,116.935839 24.275586,116.935622 24.276329,116.935787 24.277718,116.935856 24.277935,116.936657 24.278834,116.9375 24.279592,116.938136 24.280732,116.938136 24.281309,116.937927 24.282066,116.937727 24.282834,116.937309 24.28359,116.936692 24.284167,116.935857 24.284743,116.935231 24.285121,116.934396 24.285509,116.932509 24.285892,116.930631 24.286088,116.929483 24.286208,116.925762 24.285352,116.920842 24.283244,116.919313 24.283211,116.918157 24.283386,116.916558 24.284556,116.914829 24.286313,116.914256 24.287727,116.914256 24.291413,116.914248 24.295322,116.913865 24.29665,116.913335 24.298062,116.913136 24.299101,116.913118 24.300451,116.913153 24.300633,116.913362 24.30216,116.913162 24.302917,116.912953 24.303874,116.912754 24.304822,116.912754 24.305778,116.912554 24.306924,116.912563 24.308061,116.912571 24.309208,116.91278 24.310543,116.912998 24.311491,116.913423 24.312439,116.914057 24.313587,116.9149 24.314535,116.919532 24.320994,116.918906 24.32156,116.918289 24.322327,116.917873 24.322904,116.917256 24.323661,116.916204 24.324429,116.91517 24.325005,116.914336 24.325771,116.913294 24.326536,116.912251 24.326924,116.911417 24.327313,116.910575 24.32769,116.909958 24.328458,116.909333 24.329223,116.909124 24.330171,116.909133 24.331317,116.909142 24.332462,116.908942 24.333601,116.908942 24.334747,116.908951 24.335892,116.90896 24.337029,116.90876 24.338745,116.908561 24.340459,116.908256 24.341763,116.903932 24.345453,116.898184 24.348831,116.896136 24.350708,116.895719 24.354086,116.896944 24.360095,116.899401 24.363852,116.902683 24.367984,116.903908 24.369863,116.904784 24.37113,116.905393 24.372258,116.905393 24.374363,116.90396 24.378012,116.903274 24.38011,116.904559 24.380596,116.906131 24.380596,116.908693 24.380557,116.910587 24.380399,116.914088 24.379732,116.915747 24.379417,116.925342 24.381049,116.928394 24.382592,116.934881 24.390036,116.936099 24.398274,116.939536 24.414046,116.942782 24.422936,116.944027 24.431863,116.941713 24.439489,116.939425 24.448489,116.93586 24.456827,116.927104 24.468258,116.924523 24.47566,116.917354 24.490275,116.915269 24.496981,116.913879 24.5016,116.913263 24.506897,116.911473 24.515658,116.913116 24.520905,116.918477 24.523776,116.92209 24.526619,116.92738 24.517714,116.929438 24.513586,116.930672 24.512836,116.932317 24.513966,116.934371 24.518476,116.935603 24.521858,116.935601 24.526741,116.933954 24.530494,116.932492 24.534109,116.934932 24.536307,116.940032 24.539634,116.947732 24.544989,116.967145 24.544835,116.974479 24.545381,116.980767 24.544572,116.988539 24.542587,116.996807 24.540593,117.006245 24.544313,117.011682 24.549489,117.017155 24.555816,117.021695 24.564912,117.023017 24.576828,117.023009 24.586013,117.025258 24.594695,117.025206 24.602045,117.01497 24.615793,117.016155 24.622431,117.016548 24.627475,117.019868 24.629017,117.026907 24.627733,117.037465 24.626149,117.043474 24.633606,117.045707 24.640949,117.045961 24.641322,117.046459 24.642401,117.046709 24.645125,117.046582 24.649959,117.046948 24.651823,117.046949 24.653294,117.046358 24.654125,117.045341 24.656064,117.044209 24.657262,117.042296 24.660454,117.040085 24.66388,117.037559 24.666393,117.035102 24.668211,117.033395 24.669804,117.033327 24.671818,117.036528 24.675843,117.037087 24.678824,117.037715 24.681525,117.039828 24.683335,117.039691 24.685917,117.039117 24.687631,117.039431 24.689068,117.039355 24.690792,117.038062 24.69264,117.036206 24.693098,117.031306 24.693197,117.027992 24.69344,117.026888 24.693939,117.026889 24.696704,117.026614 24.699971,117.024682 24.703234,117.021093 24.70624,117.018608 24.708998,117.018536 24.709234,117.020058 24.720882,117.02262 24.731398,117.023849 24.739872,117.024277 24.755484,117.022727 24.763781,117.022596 24.76838,117.023494 24.773646,117.027973 24.780217,117.034933 24.785135,117.04238 24.789351,117.047187 24.789712,117.052126 24.793418,117.054257 24.798529,117.053926 24.805195,117.052847 24.81211,117.053788 24.818751,117.056705 24.823745,117.063815 24.825206,117.069191 24.821961,117.070104 24.818038,117.072087 24.80766,117.069781 24.797143,117.069485 24.795311,117.069859 24.79048,117.070746 24.785865,117.073668 24.782358,117.080703 24.781057,117.089908 24.785451,117.09096 24.787264,117.096304 24.788982,117.102367 24.789533,117.110642 24.787964,117.130454 24.784738,117.137505 24.784795,117.144217 24.790372,117.150712 24.806751,117.154414 24.813319,117.158766 24.816196,117.166211 24.821291,117.169366 24.826036,117.170018 24.840262,117.171786 24.849635,117.171241 24.85723,117.172238 24.865932,117.176091 24.870911,117.176162 24.870919,117.184663 24.874909,117.187039 24.876166,117.187732 24.877482,117.188418 24.879602,117.189424 24.88098,117.191428 24.881209,117.191626 24.881088,117.195941 24.883644,117.198 24.885164,117.198383 24.886641,117.200387 24.890716,117.200258 24.893289,117.197561 24.893866,117.196762 24.894361,117.19648 24.895662,117.196947 24.896453,117.196613 24.90082,117.195004 24.901604,117.190782 24.902297,117.189479 24.903039,117.188611 24.90395,117.188116 24.905313,117.187864 24.906432,117.186866 24.908526,117.184948 24.913559,117.185229 24.917328,117.184681 24.91909,117.183858 24.920601,117.183585 24.922109,117.184416 24.925877,117.187442 24.925114,117.190468 24.924099,117.196795 24.922824,117.205328 24.922556,117.210281 24.921033,117.213851 24.916247,117.21687 24.91096,117.219618 24.908439,117.223748 24.907925,117.227054 24.909174,117.229541 24.913441,117.232418 24.916561,117.233066 24.918157,117.234819 24.919931,117.237459 24.923375,117.237071 24.930824,117.237886 24.932944,117.241022 24.937579,117.241777 24.939645,117.24209 24.941369,117.243532 24.942973,117.246232 24.942834,117.247573 24.950782,117.245906 24.954038,117.240299 24.954434,117.234563 24.954541,117.234451 24.959371,117.236811 24.962527,117.242661 24.963993,117.258211 24.969362,117.267941 24.974186,117.274424 24.980009,117.2797 24.987697,117.286611 24.990519,117.292341 24.987209,117.292582 24.986854,117.293659 24.984334,117.293787 24.984242,117.286557 24.975754,117.286551 24.973241,117.288757 24.971731,117.295395 24.975749,117.30092 24.975746,117.306979 24.968203,117.310845 24.967196,117.314165 24.968703,117.314552 24.968966,117.315591 24.968218,117.318054 24.968335,117.320318 24.969264,117.32054 24.969454,117.322235 24.970952,117.329654 24.973726,117.335186 24.975233,117.341276 24.97875,117.34901 24.977239,117.355079 24.973716,117.361701 24.971989,117.364154 24.971985,117.3659 24.972096,117.368334 24.972662,117.370269 24.973511,117.371832 24.974701,117.372958 24.975948,117.373899 24.977763,117.374963 24.979237,117.376965 24.98139,117.377998 24.982304,117.383838 24.984746,117.388251 24.98323,117.393762 24.980706,117.40149 24.981192,117.413069 24.980155,117.420374 24.978883,117.421911 24.979647,117.425147 24.978184,117.429588 24.974623,117.434669 24.967826,117.441542 24.961902,117.445521 24.959956,117.452045 24.959321,117.460594 24.958626,117.469677 24.95952,117.473677 24.95872,117.483726 24.958204,117.487533 24.959014,117.493149 24.962071,117.495461 24.971422,117.493887 24.985711,117.493368 24.993539,117.491777 25.007367,117.490557 25.016821,117.492021 25.023671,117.496477 25.029288,117.499965 25.036307,117.502286 25.039085,117.503893 25.041478,117.505875 25.048316,117.506395 25.057031,117.50714 25.06505,117.508594 25.071209,117.510291 25.089344,117.510844 25.09033,117.50625 25.100952,117.503036 25.109133,117.490881 25.115886,117.481964 25.120392,117.478726 25.123379,117.478743 25.12783,117.483644 25.134477,117.484489 25.142633,117.48534 25.152271,117.484557 25.159695,117.484577 25.164887,117.486961 25.166423,117.489051 25.165214,117.49349 25.164172,117.495439 25.165174,117.495449 25.16771,117.495185 25.171261,117.496873 25.173548,117.497423 25.174291,117.505201 25.176268,117.511033 25.176231,117.515757 25.175694,117.519651 25.176937,117.525501 25.180702,117.529404 25.185494,117.531503 25.198232,117.53336 25.200684,117.535031 25.201181,117.536969 25.199394,117.540839 25.195058,117.546665 25.192234,117.548163 25.192226,117.548908 25.194898,117.546492 25.200846,117.546008 25.204026,117.54464 25.206467,117.54445 25.209436,117.545653 25.210991,117.545982 25.211028,117.548459 25.211473,117.549688 25.211249,117.552174 25.211469,117.554148 25.211693,117.555387 25.211686,117.557924 25.208135,117.558591 25.206716,117.557794 25.202727,117.556953 25.197911,117.555714 25.194209,117.556511 25.190496,117.559352 25.188253,117.562193 25.187495,117.564645 25.189338,117.568302 25.189316,117.57319 25.188921,117.575625 25.187054,117.576423 25.184824,117.574602 25.183511,117.574507 25.183216,117.574749 25.182028,117.576717 25.179969,117.579352 25.177029,117.579422 25.176973,117.582084 25.174881,117.585734 25.173651,117.590626 25.173629,117.596726 25.171377,117.599146 25.167658,117.604024 25.1643,117.608504 25.16317,117.614677 25.163363,117.618412 25.162376,117.621313 25.161102,117.621591 25.160965,117.621834 25.159505,117.621808 25.154684,117.619749 25.150981,117.620556 25.148012,117.62662 25.138346,117.631529 25.14167,117.633572 25.141292,117.63478 25.138323,117.635075 25.135745,117.636631 25.134618,117.638299 25.134169,117.640811 25.134036,117.646819 25.134464,117.649192 25.134456,117.653027 25.134066,117.657974 25.132212,117.659305 25.131409,117.66167 25.131201,117.663714 25.130328,117.663775 25.130295,117.666044 25.12898,117.668506 25.127214,117.670341 25.1259,117.671532 25.124402,117.671505 25.118471,117.670653 25.11328,117.671435 25.106972,117.671391 25.098811,117.673808 25.0925,117.677452 25.088038,117.681922 25.084689,117.687209 25.081709,117.694966 25.081315,117.699027 25.078706,117.703887 25.072386,117.707529 25.067925,117.709163 25.067919,117.710806 25.070139,117.713275 25.073841,117.719003 25.077159,117.721862 25.077891,117.724295 25.076028,117.72671 25.071194,117.727491 25.065628,117.727465 25.060434,117.729576 25.055883,117.731192 25.05328,117.731582 25.051053,117.728298 25.046614,117.726239 25.043656,117.727455 25.042537,117.732337 25.040293,117.738425 25.037301,117.74004 25.034697,117.739345 25.029453,117.738355 25.027792,117.737156 25.026532,117.735897 25.025843,117.735663 25.025793,117.735611 25.025719,117.731519 25.023511,117.723752 25.019091,117.720085 25.01799,117.718025 25.015029,117.717182 25.010581,117.716338 25.005393,117.715513 25.00354,117.712662 25.003178,117.708594 25.004305,117.704516 25.004687,117.701491 25.0045,117.699691 25.003613,117.698873 25.002225,117.696221 24.999128,117.695204 24.997034,117.695508 24.994208,117.695064 24.992204,117.693047 24.991,117.690707 24.989969,117.687559 24.988713,117.685098 24.988542,117.683785 24.990156,117.682202 24.992156,117.681011 24.993877,117.678871 24.995256,117.676384 24.997005,117.675289 24.998186,117.673871 24.998793,117.672862 24.999292,117.671697 24.999792,117.670818 24.999894,117.669809 24.999482,117.668548 24.998718,117.667131 24.99807,117.666487 24.997828,117.664104 24.997798,117.661417 24.997697,117.659286 24.997782,117.657695 24.99776,117.656382 24.997565,117.655095 24.997209,117.65446 24.996967,117.654651 24.995806,117.654843 24.994606,117.654903 24.993341,117.654721 24.991906,117.65459 24.991048,117.654347 24.989958,117.654155 24.989046,117.654346 24.987781,117.654911 24.986634,117.655416 24.985259,117.656172 24.983941,117.656172 24.982738,117.655738 24.982108,117.654789 24.981362,117.653781 24.980956,117.652833 24.980553,117.651694 24.979916,117.650816 24.979521,117.650059 24.978773,117.649242 24.978197,117.648364 24.97774,117.646911 24.977104,117.646094 24.976753,117.645216 24.97635,117.644277 24.975945,117.643138 24.97509,117.642382 24.974516,117.641634 24.973597,117.641339 24.973123,117.637036 24.970923,117.627076 24.967434,117.618233 24.964447,117.613803 24.960442,117.609348 24.952918,117.604364 24.94791,117.603782 24.942382,117.606508 24.934831,117.607037 24.930303,117.610874 24.927272,117.618594 24.925738,117.626317 24.925711,117.630176 24.9257,117.632374 24.923682,117.632635 24.922067,117.632235 24.9205,117.632043 24.916376,117.631469 24.912874,117.628949 24.910859,117.627255 24.908978,117.625803 24.906166,117.624483 24.904274,117.621433 24.902288,117.618792 24.900808,117.616022 24.899372,117.614884 24.898004,117.61438 24.895134,117.61431 24.893139,117.614744 24.889871,117.616872 24.88534,117.617184 24.882295,117.616107 24.880124,117.616732 24.877077,117.618548 24.875529,117.620311 24.874547,117.622004 24.873521,117.623064 24.870935,117.62375 24.867962,117.624749 24.866713,117.629103 24.867034,117.632509 24.867513,117.633734 24.867384,117.636594 24.866867,117.637958 24.866124,117.639384 24.864815,117.640557 24.863782,117.642078 24.861574,117.64273 24.861061,117.643686 24.859866,117.644869 24.858957,117.645547 24.858784,117.647103 24.858665,117.648294 24.85889,117.649555 24.85963,117.651476 24.860016,117.652355 24.859889,117.653798 24.858424,117.655302 24.856712,117.656624 24.854588,117.657563 24.853502,117.661285 24.854125,117.665059 24.85464,117.668381 24.853975,117.671425 24.851227,117.67359 24.849462,117.676078 24.848436,117.678878 24.847974,117.680131 24.848027,117.68187 24.848306,117.683436 24.848983,117.685627 24.85085,117.687349 24.852663,117.694298 24.856051,117.705697 24.859591,117.706088 24.859589,117.706853 24.860122,117.709817 24.861613,117.711583 24.863619,117.712408 24.866651,117.713295 24.868833,117.714434 24.871068,117.716076 24.872327,117.71778 24.874271,117.720674 24.876266,117.722629 24.87736,117.725905 24.879297,117.729997 24.880781,117.733706 24.88129,117.736972 24.880887,117.739925 24.879159,117.742434 24.878409,117.745829 24.878113,117.750412 24.87719,117.75299 24.875977,117.753667 24.873969,117.754604 24.872763,117.757181 24.872009,117.759558 24.870807,117.76112 24.869534,117.762569 24.868388,117.764894 24.869811,117.766785 24.870956,117.768918 24.870944,117.770228 24.868537,117.770592 24.866186,117.771147 24.863944,117.771892 24.86171,117.772326 24.859993,117.772265 24.858504,117.77145 24.857586,117.770565 24.855931,117.772065 24.853918,117.773687 24.852311,117.77575 24.850927,117.778386 24.851092,117.78253 24.852961,117.785234 24.853983,117.787748 24.855521,117.79001 24.857176,117.791588 24.859577,117.79222 24.86192,117.793295 24.864259,117.794491 24.865236,117.797558 24.865396,117.799941 24.862346,117.800183 24.859482,117.800668 24.856951,117.802357 24.854882,117.80266 24.852649,117.805796 24.852349,117.80849 24.85222,117.810802 24.850777,117.812552 24.849971,117.815003 24.850929,117.815826 24.852754,117.81677 24.85516,117.819914 24.856517,117.822105 24.858,117.822053 24.860006,117.820053 24.861735,117.819845 24.861883,117.8222 24.863534,117.824972 24.866531,117.822798 24.870066,117.821179 24.874603,117.824505 24.878599,117.830021 24.881575,117.833866 24.881546,117.838247 24.879001,117.844274 24.87594,117.849756 24.873888,117.853056 24.87487,117.856927 24.877353,117.862428 24.878318,117.864013 24.878307,117.865044 24.87686,117.866984 24.875535,117.868543 24.874324,117.869982 24.872536,117.871671 24.871782,117.873309 24.873495,117.873439 24.875841,117.873448 24.87819,117.874782 24.880934,117.876984 24.882535,117.878873 24.88476,117.880078 24.886927,117.881153 24.88887,117.881846 24.890526,117.881422 24.892651,117.88111 24.894657,117.882558 24.895686,117.884751 24.895039,117.886883 24.894004,117.888695 24.892621,117.891322 24.89049,117.892362 24.88918,117.89238 24.889078,117.893308 24.88773,117.893628 24.887209,117.89375 24.886503,117.893498 24.885479,117.893394 24.885331,117.892593 24.884185,117.892388 24.88252,117.892542 24.881636,117.893905 24.879827,117.894287 24.879119,117.89466 24.878791,117.894746 24.878653,117.896602 24.87649,117.898614 24.874659,117.899464 24.874247,117.900765 24.874157,117.901424 24.874057,117.903802 24.873367,117.905988 24.871519,117.907923 24.870083,117.910439 24.869842,117.913017 24.870008,117.913026 24.870305,117.916029 24.868437,117.918217 24.867125,117.920153 24.865399,117.921785 24.864306,117.924718 24.864252,117.92624 24.862686,117.927708 24.861769,117.927829 24.861691,117.927977 24.86162,117.928072 24.861563,117.928229 24.861442,117.928437 24.86131,117.929245 24.859474,117.930558 24.857802,117.937637 24.848698,117.938111 24.847261,117.940365 24.844662,117.943112 24.843141,117.946971 24.843124,117.956371 24.845093,117.95878 24.844595,117.961294 24.842994,117.962529 24.842025,117.962964 24.841171,117.962598 24.838428,117.961771 24.835099,117.960432 24.831958,117.960988 24.82874,117.960353 24.826792,117.959144 24.82399,117.960074 24.821288,117.961388 24.820253,117.961118 24.816932,117.960231 24.814416,117.958717 24.813096,117.955943 24.81253,117.957134 24.810926,117.958195 24.808801,117.959369 24.806052,117.959856 24.803423,117.960543 24.800151,117.960777 24.797289,117.961316 24.792294,117.961308 24.788114,117.960724 24.784849,117.960202 24.781989,117.959115 24.77821,117.958027 24.775634,117.956757 24.773283,117.954992 24.771284,117.955296 24.767962,117.955157 24.765616,117.954991 24.765453,117.952843 24.763756,117.949887 24.762786,117.94787 24.762451,117.947174 24.760613,117.946201 24.758758,117.945366 24.755664,117.944974 24.752688,117.944548 24.750415,117.944522 24.750278,117.943818 24.748106,117.942679 24.745188,117.941036 24.742496,117.941088 24.739634,117.941444 24.736878,117.94187 24.733725,117.943234 24.729539,117.943851 24.726395,117.945146 24.72146,117.945824 24.718079,117.946997 24.71441,117.948371 24.711714,117.951379 24.709932,117.955518 24.708497,117.956388 24.707283,117.956309 24.703852,117.955987 24.701445,117.956787 24.698299,117.9579 24.694684,117.959778 24.690838,117.961266 24.687511,117.962318 24.684815,117.963562 24.681209,117.964301 24.677595,117.965405 24.674043,117.965405 24.671009,117.963439 24.668889,117.959533 24.66735,117.957768 24.666148,117.957828 24.663919,117.95888 24.661738,117.958619 24.659563,117.957167 24.6579,117.956149 24.65573,117.955706 24.654123,117.955505 24.651724,117.95474 24.649262,117.952401 24.647367,117.951189 24.646934,117.950738 24.646692,117.950234 24.646882,117.949141 24.646656,117.948104 24.646621,117.947748 24.646544,117.947331 24.646158,117.946863 24.645917,117.946675 24.645502,117.946128 24.644981,117.945542 24.644567,117.943457 24.643437,117.942967 24.642646,117.942672 24.641459,117.942379 24.6413,117.941781 24.640594,117.941528 24.64007,117.941526 24.639577,117.941351 24.639018,117.940796 24.638382,117.940447 24.638211,117.940195 24.637986,117.939671 24.637771,117.938317 24.637598,117.937417 24.637622,117.936931 24.637257,117.934963 24.636128,117.931879 24.634184,117.930306 24.632639,117.929046 24.630588,117.927648 24.627949,117.926006 24.62515,117.924859 24.62189,117.923339 24.618512,117.922271 24.616515,117.920691 24.614231,117.918425 24.612743,117.915421 24.61287,117.9126 24.613047,117.909276 24.613348,117.906013 24.613013,117.901632 24.612002,117.898431 24.612817,117.895491 24.613513,117.893618 24.61301,117.89236 24.611583,117.892473 24.609001,117.893331 24.606016,117.892767 24.603502,117.893001 24.600694,117.893365 24.597775,117.892602 24.596108,117.891284 24.593653,117.888639 24.591315,117.886012 24.590159,117.887443 24.58828,117.889939 24.587018,117.892315 24.585577,117.8945 24.58476,117.896616 24.582924,117.898299 24.580736,117.900554 24.579062,117.902801 24.577514,117.905612 24.576188,117.90818 24.574745,117.910792 24.572673,117.913603 24.568594,117.913213 24.565157,117.910687 24.562183,117.909923 24.55921,117.90846 24.555353,117.908829 24.553141,117.909948 24.55022,117.91138 24.548324,117.913125 24.546653,117.912994 24.544019,117.912039 24.541966,117.911406 24.539785,117.911275 24.537034,117.911587 24.535667,117.911701 24.534265,117.911822 24.533894,117.912065 24.533469,117.912342 24.533154,117.912777 24.532755,117.913427 24.53241,117.914599 24.532119,117.915493 24.531888,117.916986 24.531711,117.918445 24.531701,117.921111 24.531914,117.923203 24.531842,117.9254 24.530915,117.927719 24.528856,117.927754 24.528799,117.931246 24.526649,117.933965 24.522614,117.935033 24.518588,117.935659 24.515609,117.936301 24.515254,117.937492 24.513418,117.939934 24.512558,117.943002 24.513057,117.944505 24.512311,117.945627 24.510247,117.947183 24.508354,117.949252 24.506973,117.952069 24.505653,117.954321 24.503991,117.955434 24.50135,117.958573 24.501229,117.962187 24.485966,117.961504 24.484447,117.962182 24.482205,117.962356 24.480374,117.96228 24.480313,117.962257 24.480259,117.962286 24.480125,117.960637 24.475898,117.957971 24.473302,117.957745 24.472631,117.957797 24.472006,117.958293 24.471608,117.959467 24.470923,117.960772 24.470576,117.960763 24.469895,117.960693 24.469044,117.959998 24.467346,117.959249 24.466214,117.958223 24.464426,117.957892 24.463368,117.958049 24.462154,117.956901 24.459973,117.956814 24.459191,117.958561 24.453725,117.961022 24.44914,117.964075 24.444063,117.968086 24.440104,117.971766 24.436633,117.975037 24.434878,117.976047 24.434694,117.980389 24.434092,117.981608 24.434015,117.983531 24.433864,117.985081 24.433524,117.987091 24.43246,117.988214 24.431131,117.989006 24.429981,117.990311 24.427006,117.991016 24.42529,117.991564 24.424142,117.992522 24.423072,117.994411 24.422542,117.995952 24.422013,117.999216 24.420684,118.000139 24.420266,118.002106 24.419511,118.005519 24.418621,118.005971 24.418661,118.01714 24.419651,118.028481 24.420539,118.036678 24.419836,118.040246 24.419531,118.04383 24.418939,118.049015 24.418307" 8 | -------------------------------------------------------------------------------- /src/test/resources/input/data.csv: -------------------------------------------------------------------------------- 1 | LON,LAT,VALUE 2 | 117.633874,24.571444,57 3 | 117.638334,24.555557,46 4 | 117.722448,24.516496,51 5 | 117.629265,24.560098,58 6 | 117.611203,24.595875,56 7 | 117.611025,24.616869,64 8 | 117.655949,24.585522,37 9 | 117.563275,24.583678,61 10 | 117.661744,24.512879,41 11 | 117.590707,24.567298,49 12 | 117.592138,24.561778,60 13 | 117.75041,24.582571,26 14 | 117.718614,24.553821,22 15 | 117.7219,24.525018,50 16 | 117.675755,24.527779,34 17 | 117.609957,24.547279,57 18 | 117.712172,24.527286,49 19 | 117.722717,24.53155,55 20 | 117.727696,24.533447,55 21 | 117.738893,24.52207,62 22 | 117.607096,24.588255,56 23 | 117.738053,24.519465,59 24 | 117.731282,24.522476,56 25 | 117.603979,24.56459,67 26 | 117.626356,24.548717,55 27 | 117.724452,24.551386,35 28 | 117.740504,24.560657,53 29 | 117.618961,24.549018,54 30 | 117.608994,24.530418,56 31 | 117.622495,24.570095,54 32 | 117.627833,24.579964,46 33 | 117.582143,24.601229,54 34 | 117.715339,24.496504,38 35 | 117.665902,24.515726,34 36 | 117.623611,24.566635,60 37 | 117.592994,24.554124,59 38 | 117.597276,24.557116,64 39 | 117.611723,24.542526,55 40 | 117.602251,24.544097999999998,62 41 | 117.622139,24.544766,57 42 | 117.61526,24.544961,58 43 | 117.607818,24.534733,54 44 | 117.614644,24.526666,60 45 | 117.335557,24.391322,73 46 | 117.333475,24.386853,72 47 | 117.630425,24.525979,59 48 | 117.636004,24.541769,55 49 | 117.624638,24.557617,57 50 | 117.615809,24.564733,63 51 | 117.611562,24.5662,59 52 | 117.615426,24.568105,59 53 | 117.621174,24.56797,57 54 | 117.624476,24.571586,48 55 | 117.613896,24.583813,57 56 | 117.620555,24.536389,55 57 | 117.62928,24.508709,60 58 | 117.636528,24.507608,63 59 | 117.621688,24.512162,59 60 | 117.675645,24.525531,40 61 | 117.588212,24.566013,64 62 | 117.533333,24.580556,62 63 | 117.483322,24.461977,65 64 | 117.489943,24.484498,46 65 | 117.63837,24.547568,55 66 | 117.653113,24.530548,43 67 | 117.669903,24.53795,38 68 | 117.671315,24.545752,43 69 | 117.672849,24.550436,15 70 | 117.645,24.530276,59 71 | 117.500848,24.48797,51 72 | 117.492808,24.50087,55 73 | 117.345803,24.396264,80 74 | 117.351309,24.412266,63 75 | 117.455162,24.465381,82 76 | 117.469416,24.473238,49 77 | 117.395855,24.408754,95 78 | 117.297986,24.367207,65 79 | 117.31673,24.370186,78 80 | 117.305395,24.374229,69 81 | 117.323234,24.374966,75 82 | 117.324537,24.380773,79 83 | 117.329023,24.361591,83 84 | 117.31307,24.357892,67 85 | 117.322896,24.352516,92 86 | 117.328954,24.35711,76 87 | 117.343588,24.398142,76 88 | 117.381544,24.437307,58 89 | 117.322308,24.360719,65 90 | 117.425715,24.436276,73 91 | 117.313228,24.344604,69 92 | 117.471337,24.498012,46 93 | 117.431175,23.703669,28 94 | 117.427373,23.722305,26 95 | 117.643025,24.552256,55 96 | 117.65156,24.550842,49 97 | 117.651386,24.540223,38 98 | 117.671681,24.524612,43 99 | 117.54998,24.538124,58 100 | 117.526333,24.543996,58 101 | 117.529964,24.51167,48 102 | 117.406182,23.685863,37 103 | 117.412965,23.694929,15 104 | 117.406572,23.700403,27 105 | 117.41259,23.715527,25 106 | 117.417331,23.706253,23 107 | 117.41750976562503,23.678692220053,27 108 | 117.43251193576401,23.672512749566,27 109 | 117.42870985243098,23.690521375869,23 110 | 117.582592230903,24.529037543403,72 111 | 117.56562934027801,24.514277886285,63 112 | 117.55767876519099,24.508606770834,56 113 | 117.560999348959,24.542778320313,43 114 | 117.57894911024403,24.554859483507,63 115 | 117.585763888889,24.55863905165,61 116 | 117.63415500217098,24.474407552084,60 117 | 117.645703396268,24.469998914931,55 118 | 117.71123209635499,24.455647515191,51 119 | 117.69505967882003,24.480706922744,61 120 | 117.665124511719,24.484951171875,68 121 | 117.44441216362901,23.707445203994,26 122 | 117.460082,23.704721,23 123 | 117.478266601563,23.706957465278,22 124 | 117.48996554904602,23.740160319011,18 125 | 117.505431857639,23.74636420356,18 126 | 117.522054036459,23.742019042969,37 127 | 117.51949327257,23.729182671441,18 128 | 117.50216010199699,23.73082546658,28 129 | 117.47852322048698,23.725588650174,25 130 | 117.48748996310803,23.720949164497,21 131 | 117.39313340928902,23.686220974393,11 132 | 117.373743489584,23.93575873481,24 133 | 117.41970865885503,23.919810384115,35 134 | 117.25070366753499,23.728325737848,32 135 | 117.19687499999998,23.706448296441,46 136 | 117.1335804579,23.705083821615,36 137 | 117.13470269097297,23.725178222657,29 138 | 117.17054009331599,23.700083550348,null 139 | 117.85622151692803,24.517155219185,33 140 | 117.27677191840303,23.747917751737,26 141 | 117.26469509548701,23.750971408421,36 142 | 117.256633572049,23.741143391928,40 143 | 117.194871690539,23.718263888889,42 144 | 117.187819281685,23.697556423612,41 145 | 117.159790852865,23.713974066841,27 146 | 117.17104790581601,23.708651529948,50 147 | 117.18452229817797,23.709455023872,42 148 | 117.17609456380302,23.719035644532,25 149 | 117.31290798611201,23.971284179688,31 150 | 117.32350857204898,23.965405815973,34 151 | 117.33385145399399,23.940863444011,26 152 | 117.33105984157999,23.955868055556,31 153 | 117.328711480035,23.945394694011,28 154 | 117.88001546224001,24.502885742188,44 155 | 117.393650716146,23.830609266494,16 156 | 117.38813123914997,23.822201334636,17 157 | 117.376756456164,23.825397135417,16 158 | 117.38502468533,23.852506510417,27 159 | 117.384041069879,23.831901312935,28 160 | 117.37580132378503,23.81646077474,22 161 | 117.36986436632003,23.812247450087,25 162 | 117.38122477213597,23.821237250435,27 163 | 117.38692409939301,23.841242947049,26 164 | 117.34420166015701,23.956585557726,25 165 | 117.37185384114599,23.896554633247,26 166 | 117.39411268446202,23.849988878039,25 167 | 117.37533989800397,23.875198838976,29 168 | 117.145467664931,23.711408691407,25 169 | 117.15429009331604,23.724572211372,20 170 | 117.84291286892403,24.536749945747,46 171 | 117.871688639323,24.515615234375,25 172 | 117.87096950954901,24.510812717014,28 173 | 117.87704888237897,24.49471842448,27 174 | 117.88249620225702,24.521783040365,21 175 | 117.89954074435798,24.529040256077,25 176 | 117.910335828994,24.534500054254,25 177 | 117.93680718316,24.487095269098,19 178 | 117.95012559678901,24.473264973959,18 179 | 117.654634874132,24.57723687066,55 180 | 117.61654785156298,24.539019911025,62 181 | 117.62949435763903,24.560407714844,57 182 | 117.59616536458401,24.56902967665,61 183 | 117.60595838758701,24.549888780382,53 184 | 117.93380995008698,24.470890028212,23 185 | 117.92979248046902,24.481177571615,18 186 | 117.91709092882002,24.501670193143,20 187 | 117.35689887152802,23.935012749566,34 188 | 117.35544813368097,23.964742567275,25 189 | 117.32188178168497,23.948940700955,35 190 | 117.33784179687501,23.927811143664,28 191 | 117.90384901258699,24.518908691407,24 192 | 117.89561225043497,24.532963324653,35 193 | 117.61374565972301,24.558231608073,57 194 | 117.622725965712,24.554671766494,45 195 | 117.61670762803897,24.551230740018,58 196 | 117.62014105902801,24.526811252171,58 197 | 117.61202555338599,24.519781629775,46 198 | 117.684663628473,24.533626844619,36 199 | 117.64730061849002,24.539517144098,52 200 | 117.63666558159798,24.530281032987,62 201 | 117.622252,24.629715,60 202 | 117.61890326605999,24.622203776042,51 203 | 117.869482,24.491796,27 204 | 117.911141,24.494068,25 205 | 117.943532,24.469689,42 206 | 117.947265,24.478825,32 207 | 117.942307,24.478538,29 208 | 117.923132,24.507817,24 209 | 117.919105,24.511556,21 210 | 117.862811,24.521322,23 211 | 117.646162,24.512547,47 212 | 117.665411,24.508146,43 213 | 117.670385,24.517726,37 214 | 117.364798,23.886141,35 215 | 117.74465000000001,24.53027,60 216 | 117.745205,24.542698,49 217 | 117.731112,24.54017,57 218 | 117.720871,24.545025,50 219 | 117.699401,24.54191,22 220 | 117.746175,24.490344,46 221 | 117.740489,24.555461,67 222 | 117.735567,24.560287,26 223 | 117.750078,24.570437,32 224 | 117.916296,24.495413,24 225 | 117.929733,24.492645,27 226 | 117.747644,24.577647,27 227 | 117.960319,24.479748999999998,27 228 | 117.917784,24.50565,20 229 | 117.823797,24.411674,28 230 | 117.820942,24.412006,32 231 | 117.821949,24.39786,35 232 | 117.854994,24.398187,31 233 | 117.849313,24.395317,32 234 | 117.827422,24.392078,28 235 | 117.818747,24.392294,17 236 | 117.801741,24.444526,23 237 | 117.798147,24.447626,47 238 | 117.749497,24.472253,41 239 | 117.769466,24.475071,38 240 | 117.767268,24.484114,31 241 | 117.775405,24.481937,29 242 | 117.83691,24.405254,33 243 | 117.860454,24.398725,39 244 | 117.866137,24.399891,30 245 | 117.868689,24.414161,27 246 | 117.860508,24.417333,12 247 | 117.862925,24.409266,90 248 | 117.850384,24.414099,27 249 | 117.85429,24.417412,21 250 | 117.896725,24.499953,30 251 | 117.907991,24.503691,21 252 | 117.90612,24.509657,23 253 | 117.715957,24.402494,43 254 | 117.712275,24.396963,52 255 | 117.913656,24.485022999999998,23 256 | 117.774658,24.439791,31 257 | 117.794196,24.464829,42 258 | 117.943352,24.484452,30 259 | 117.949451,24.487784,24 260 | 117.927212,24.477305,27 261 | 117.889651,24.51297,15 262 | 117.894462,24.54215,22 263 | 117.844765,24.411475,33 264 | 117.839974,24.419151,30 265 | 117.825951,24.438818,null 266 | 117.832279,24.433666,35 267 | 117.858686,24.431668,21 268 | 117.852308,24.430853,29 269 | 117.846266,24.431939,42 270 | 117.836659,24.438401,25 271 | 117.877182,24.50813,27 272 | 117.851353,24.514014,30 273 | 117.8277,24.500231,31 274 | 117.30518000000001,24.361703,67 275 | 117.317051,24.356583,75 276 | 117.84698,24.466064,28 277 | 117.823262,24.47136,30 278 | 117.642211,24.558326,51 279 | 117.608197,24.554088,63 280 | 117.66759,24.49644,57 281 | 117.501328,24.587069,44 282 | 117.522829,24.580329,57 283 | 117.53164,24.596094,73 284 | 117.514148,24.589662,69 285 | 117.520232,24.586632,47 286 | 117.509138,24.579735,58 287 | 117.499919,24.578384,55 288 | 117.522534,24.592084,72 289 | 118.073266,24.344319,30 290 | 118.133006,24.277578,37 291 | 118.039315,24.335974,30 292 | 118.009722,24.324565,25 293 | 117.919873,24.494428,31 294 | 118.027713,24.405738,14 295 | 118.033122,24.405342,13 296 | 118.008682,24.400177,29 297 | 118.014966,24.405743,30 298 | 118.068339,24.373225,24 299 | 118.079837,24.378596,30 300 | 118.019677,24.341711,26 301 | 117.990671,24.311818,null 302 | 118.01097,24.302809,21 303 | 117.759854,24.462373,38 304 | 117.773843,24.477164,29 305 | 117.77068,24.462454,58 306 | 117.767981,24.450254,35 307 | 117.756555,24.47746,39 308 | 118.029326,24.393541,23 309 | 118.05177,24.411165,26 310 | 118.051549,24.398774,27 311 | 118.052686,24.391726,27 312 | 118.072288,24.394374,27 313 | 118.057925,24.404875,16 314 | 118.002532,24.398577,13 315 | 117.793285,24.452731,31 316 | 117.783532,24.452589,28 317 | 117.776867,24.455756,null 318 | 117.806237,24.431413,29 319 | 117.791544,24.448162,29 320 | 117.809981,24.450442,33 321 | 117.799785,24.434237,21 322 | 117.804015,24.442365,31 323 | 117.71988,24.496135,49 324 | 117.721035,24.510197,44 325 | 117.72204,24.534042,32 326 | 117.875598,24.40967,16 327 | 117.873608,24.323475,24 328 | 117.871151,24.331221,24 329 | 117.804448,24.369477,30 330 | 117.778379,24.361476,70 331 | 117.936438,24.312804,24 332 | 117.682073,24.509785,44 333 | 117.692183,24.5252,34 334 | 117.911553,24.367745,23 335 | 117.905081,24.358902,21 336 | 117.90627,24.365539,25 337 | 117.9026,24.370684,25 338 | 117.899417,24.365231,25 339 | 117.891197,24.381143,35 340 | 117.885213,24.384437,26 341 | 117.850502,24.442482,26 342 | 117.877389,24.380082,35 343 | 117.762918,24.741413,54 344 | 117.781058,24.749298,56 345 | 117.782,24.763461,57 346 | 117.782043,24.769508,62 347 | 117.790942,24.758314,57 348 | 117.790656,24.7656,48 349 | 117.590496,24.388514,43 350 | 117.821142,24.448582,24 351 | 117.8676,24.502689,27 352 | 117.857805,24.500308,22 353 | 117.887569,24.504631,19 354 | 117.880132,24.510657,26 355 | 117.362522,24.518754,60 356 | 117.365637,24.51356,63 357 | 117.363072,24.503333,58 358 | 117.370204,24.508963,61 359 | 117.376178,24.502387,56 360 | 117.351944,24.529231,64 361 | 117.404695,24.479928,66 362 | 117.365891,24.505815,57 363 | 117.698067,24.528265,33 364 | 117.725999,24.509227,50 365 | 117.745382,24.502696,55 366 | 117.73766,24.506898,50 367 | 117.688053,24.635425,24 368 | 117.679662,24.647755,17 369 | 117.659831,24.681577,13 370 | 117.635969,24.692761,11 371 | 117.642035,24.68331,25 372 | 117.668832,24.666077,34 373 | 117.638484,24.661624,29 374 | 117.537621,24.855871,13 375 | 117.746944,24.526829,70 376 | 117.745184,24.536077,55 377 | 117.736606,24.539085,72 378 | 117.734565,24.54558,31 379 | 117.706183,24.529167,52 380 | 117.712097,24.528951,93 381 | 117.710275,24.524837,41 382 | 117.741227,24.521635,58 383 | 118.050488,24.40372,26 384 | 118.030837,24.346369,22 385 | 117.363078,24.509968,61 386 | 117.355415,24.509841,54 387 | 117.362549,24.526627,59 388 | 117.364109,24.53416,61 389 | 117.353388,24.503851,63 390 | 117.375802,24.52017,60 391 | 117.375183,24.528387,62 392 | 117.369904,24.519083,62 393 | 117.376567,24.511762,39 394 | 117.385154,24.507705,61 395 | 117.623716,24.642464,38 396 | 117.529908,25.00585,28 397 | 117.518559,25.002524,33 398 | 117.522903,24.999424,19 399 | 117.401956,24.503445,66 400 | 117.756405,24.587327,41 401 | 117.76995,24.595879,43 402 | 117.778102,24.591693,54 403 | 117.783147,24.611428,56 404 | 117.778982,24.620249,55 405 | 117.777607,24.603854,54 406 | 117.767735,24.627155,30 407 | 117.762355,24.624835,36 408 | 117.771418,24.640034,47 409 | 117.714922,24.651476,38 410 | 117.721389,24.63834,38 411 | 117.679852,24.498652,57 412 | 117.670488,24.508402,50 413 | 117.663039,24.506931,51 414 | 117.654399,24.518791,49 415 | 117.629568,24.51557,51 416 | 117.720674,24.624919,35 417 | 117.758819,24.615432,25 418 | 117.737465,24.645865,58 419 | 117.786603,24.637855,52 420 | 117.793254,24.64958,60 421 | 117.80424,24.63913,54 422 | 117.792201,24.628901,57 423 | 117.801238,24.615658,56 424 | 117.80317,24.601436,54 425 | 117.792354,24.588232,55 426 | 117.732822,24.525595,58 427 | 117.745773,24.511674,52 428 | 117.733834,24.500987,54 429 | 117.758384,24.600269,25 430 | 117.742046,24.630808000000002,29 431 | 117.797722,24.662233,58 432 | 117.747279,24.616098,30 433 | 117.622718,23.952978,30 434 | 117.605503,23.937488,18 435 | 117.576769,23.93274,33 436 | 117.56096,23.922645,25 437 | 117.649871,24.460734,54 438 | 117.636638,24.410492,63 439 | 117.674735,24.451531,54 440 | 117.733334,24.467811,51 441 | 117.522009,24.561932,58 442 | 117.551367,24.56938,52 443 | 117.564851,24.576929,60 444 | 117.547397,23.923512,21 445 | 117.518381,23.923109,28 446 | 117.629929,23.928805,33 447 | 117.635433,23.929665,19 448 | 117.695819,24.016597,22 449 | 117.681902,23.999701,26 450 | 117.682257,23.978408,18 451 | 117.683059,23.94867,24 452 | 117.7855,24.66116,64 453 | 117.763818,24.688051,54 454 | 117.64506,23.937496,27 455 | 117.622199,23.919792,25 456 | 117.74236,24.612091,30 457 | 117.512537,24.582376,51 458 | 117.521948,24.573346,59 459 | 117.530636,24.596761,65 460 | 117.671309,23.963801,26 461 | 117.638235,24.558301,52 462 | 117.744134,24.4844,null 463 | 118.09858,24.351259,35 464 | 118.1223,24.305651,32 465 | 117.766339,24.607839,138 466 | 117.385531,24.499472,55 467 | 117.400674,24.488692,49 468 | 117.264752,23.762422,52 469 | 117.626698,24.54004,55 470 | 118.112457,24.316085,27 471 | 118.063387,24.257734,15 472 | 117.655561,24.098851,21 473 | 117.626886,24.14987,35 474 | 117.617844,24.138332,52 475 | 117.619439,24.125129,42 476 | 117.620453,24.119602,40 477 | 117.600434,24.124737,102 478 | 117.618617,24.105661,39 479 | 117.604141,24.133187,44 480 | 117.58953,24.087378,47 481 | 117.588587,24.084902,45 482 | 117.607581,24.141363,35 483 | 117.323018,24.342249,82 484 | 117.733754,24.515858,56 485 | 117.586228,24.106873,51 486 | 117.595841,24.104925,43 487 | 117.567186,24.072748,109 488 | 117.574496,24.093695,50 489 | 117.526017,24.993388,30 490 | 117.541094,25.000898,28 491 | 117.549272,25.003517,23 492 | 117.547479,24.997034,20 493 | 117.543262,24.989756,20 494 | 117.530684,24.984198,23 495 | 117.530677,25.002387,21 496 | 117.641429,23.927658,29 497 | 117.717323,24.505638,37 498 | 117.647866,24.510053,46 499 | 117.659679,24.501963,57 500 | 117.6605,24.511256,28 501 | 117.66544,24.525605,36 502 | 117.666586,23.927965,35 503 | 117.636704,23.953945,26 504 | 117.641259,24.114741,34 505 | 117.642998,24.105564,34 506 | 117.665578,24.089372,28 507 | 117.694502,24.067103,33 508 | 117.706618,24.05683,25 509 | 117.71366,24.048228,15 510 | 117.630316,24.108592,35 511 | 117.63347,24.120479,35 512 | 117.612183,24.13223,41 513 | 117.604337,24.14646,39 514 | 117.577245,24.108491,38 515 | 117.599085,24.109713,41 516 | 117.611058,24.11364,32 517 | 117.596407,24.117557,38 518 | 117.613758,24.107882,34 519 | 117.569575,24.080899,36 520 | 117.787011,24.4743,32 521 | 117.718802,24.398674,55 522 | 117.363697,24.419263,68 523 | 117.870882,24.394618,26 524 | 117.826512,24.426986,29 525 | 117.823369,24.43656,29 526 | 117.820843,24.442759,26 527 | 117.814758,24.435003,29 528 | 117.837186,24.447909,89 529 | 117.831769,24.466766,28 530 | 117.728812,24.396878,35 531 | 117.884593,24.461977,35 532 | 117.869992,24.463889,25 533 | 117.634284,24.631854,39 534 | 117.570521,24.587085,54 535 | 117.553544,24.579111,55 536 | 117.537701,24.569666,43 537 | 117.660219,24.522215,39 538 | 117.799282,24.394393,35 539 | 117.810245,24.403426,34 540 | 117.847511,24.423651,27 541 | 117.646481,24.423663,56 542 | 117.663626,24.505678,46 543 | 117.654056,24.506152,55 544 | 117.716495,24.499922,45 545 | 117.712691,24.513489,39 546 | 117.614301,24.099574,31 547 | 117.715647,24.534255,38 548 | 117.723869,24.505903,48 549 | 117.643538,24.521401,43 550 | 117.649422,24.503862,null 551 | 118.041909,24.363721,27 552 | 117.687377,24.516292,41 553 | 117.681948,24.07653,36 554 | 117.705399,24.044804,20 555 | 117.616519,24.148927,33 556 | 117.623203,24.134063,34 557 | 117.727558,24.52074,null 558 | 117.985191,24.396622,28 559 | 117.700263,24.497173,28 560 | 118.0086,24.345849,25 561 | 117.701814,24.521452,29 562 | 117.70754,24.508388,56 563 | 117.685579,24.508219,35 564 | 117.709083,24.496877,null 565 | 117.671213,24.490292,60 566 | 118.090626,24.348256,28 567 | 117.699306,24.506554,49 568 | 117.74848,24.484968,39 569 | 117.683576,24.511909,32 570 | 117.702726,24.501632,50 571 | 117.733553,24.497298,36 572 | 117.62756,24.617717,70 573 | 117.654745,24.522719,36 574 | 117.740099,24.494187,34 575 | 117.677757,24.537667,45 576 | 117.691864,24.531797,45 577 | 117.655603,24.523589,38 578 | 117.735712,24.484436,53 579 | 118.043804,24.354345,21 580 | 117.687417,24.489332,45 581 | 117.60488,24.590062,56 582 | 117.746125,24.567567,41 583 | 117.725216,24.490337,38 584 | 117.742687,24.563952,33 585 | 117.679258,24.521515,42 586 | 117.730254,24.481957,35 587 | 117.715285,24.327441,32 588 | 117.690017,24.498448,44 589 | 117.740776,24.512722,55 590 | 117.33686,23.969579,null 591 | 117.350451,23.926404,23 592 | 117.7056,24.540204,37 593 | 117.635315,24.498207999999998,89 594 | 117.598187,24.466972,61 595 | 117.72685,24.498121,46 596 | 117.678158,24.487034,72 597 | 117.697539,24.515851,41 598 | 117.679048,24.506744,null 599 | 117.766374,24.533994,42 600 | 117.702982,24.523535,35 601 | 117.692464,24.509426,41 602 | 117.385295,23.882721,56 603 | 118.058353,24.35896,null 604 | 117.226153,23.69591,25 605 | 117.926073,24.338485,36 606 | 117.636424,24.526762,5 607 | 117.843913,24.490983,20 608 | 117.738917,24.522818,56 609 | 117.714882,24.501889,27 610 | 117.666893,24.515443,18 611 | 117.633312,24.528135,18 612 | 117.6336,24.4674,29 --------------------------------------------------------------------------------