Polar.java
001 /*
002
003 Polar 投影(扫描方式,自正北方向顺时针)
004
005 PACKAGE: cma.common.projection
006 FILENAME: Polar.java
007 LANGUAGE: Java2 v1.4
008 ORIGINAL: 无
009 DESCRIPTION: 极坐标投影(主要用于雷达图像处理)
010 RELATED: cma.common.projection.Lambert(兰勃特投影)
011 EDITOR: UltraEdit-32 v12.20a(Windows) NEdit(Linux)
012 CREATE: 2007-05-06 20:08:23
013 UPDATE: 2007-07-18 修改为抽象类Coordinate的扩展类
014 AUTHOR: 刘泽军 (BJ0773@gmail.com)
015 广西气象减灾研究所
016 Guangxi Institude of Meteorology and Disaster-reducing Research(GIMDR)
017
018 Compile : javac Coordinate.java Polar.java
019
020 How to use Polar class:
021
022 Polar polar = new Polar(109.24, 24.35, 512, 384, 1.0, 0.0);//构造函数
023 ...
024
025 */
026
027 /**
028 *
029 * 扫描平面
030 * /
031 * /
032 * /
033 * /
034 * /
035 * / 仰角
036 * -------------------- 0度平面
037 *
038 * 如图所示:
039 * 扫描平面=>0度平面,需要乘以cos(仰角)
040 * 0度平面=>扫描平面,需要除以cos(仰角)
041 *
042 * 注意,日常显示的雷达图是扫描平面上的图。本类所说的屏幕指扫描平面。
043 *
044 */
045
046 /**
047 * 雷达扫描示意图
048 *
049 * 359 0
050 * | radius
051 * | /
052 * | /
053 * |angle/
054 * | /
055 * | ^ /
056 * | /
057 * | /
058 * |/
059 * 270 -----------------中心----------------- 90
060 * |
061 * |
062 * |
063 * |
064 * |
065 * |
066 * |
067 * |
068 * |
069 * 180
070 */
071
072 package cma.common.projection;
073
074 import java.awt.*;
075 import java.awt.geom.*;
076 import java.lang.Math.*;
077
078 public class Polar extends Coordinate {
079
080 //私有成员
081 private double perKilometer = 1.0 ; //比例尺:一公里对应的像素点数(扫描平面)
082 private double elevation = 0.0 ; //仰角
083 private double cosineElevation = 1.0 ; //仰角的余弦值
084 private double kmPerDegreeX = 1.0 ; //1经度对应的距离(公里),不同纬度数值不同
085 private double kmPerDegreeY = 1.0 ; //1纬度对应的距离(公里),不同纬度数值不同
086
087 /**
088 * 功能:计算球面上两点间的距离(单位:公里),原在edu.gimdr.Atmos.Meteorology类中写有,为避免import过多的类,故重写一份
089 * 参数:
090 * lon1,lat1 - 第1点的位置(经纬度)
091 * lon2,lat2 - 第2点的位置(经纬度)
092 * 返回值:
093 * 球面距离
094 */
095 public static double distanceOfSphere ( double lon1, double lat1, double lon2, double lat2 ) {
096 /* 公式:
097 A(x,y) B(a,b)
098 AB点的球面距离=R*{arccos[cos(b)*cos(y)*cos(a-x)+sin(b)*sin(y)]}, by Google
099 */
100
101 double rlon1 = Math.toRadians ( lon1 ) ;
102 double rlat1 = Math.toRadians ( lat1 ) ;
103 double rlon2 = Math.toRadians ( lon2 ) ;
104 double rlat2 = Math.toRadians ( lat2 ) ;
105
106 return ( Coordinate.RADIUS* ( Math.acos ( Math.cos ( rlat2 ) *Math.cos ( rlat1 ) *Math.cos ( rlon2-rlon1 ) +Math.sin ( rlat2 ) *Math.sin ( rlat1 )))) ;
107 }
108
109 /**
110 * 功能:
111 * 重置参数
112 * 参数:
113 * lon,lat - 中心经纬度,
114 * px,py - 中心经纬度对应的屏幕坐标
115 * sc - 缩放系数
116 * agl - 仰角
117 * 返回值:
118 * 无
119 */
120 public void reset ( double lon, double lat, int px, int py, double sc, double agl ) {
121 type = Coordinate.POLAR;
122 center = new Point2D.Double (
123 lon < 0.0 ? 0.0 : lon > 360.0 ? 360.0 : lon,
124 lat < - 90.0 ? - 90.0 : lat > 90.0 ? 90.0 : lat
125 ) ;
126 place = new Point ( px, py ) ;
127 elevation = Math.toRadians ( Math.IEEEremainder ( Math.abs ( agl ) , 90.0 )) ; //在0-90度之间,但不能为90度
128 cosineElevation = Math.cos ( elevation ) ; //仰角的余弦值
129 scale = sc == 0.0 ? 1.0 : Math.abs ( sc ) ; //缩放系数
130 scaleOriginal = scale;
131 offset = new Point ( 0 , 0 ) ;
132
133 perKilometer = 1.0 ; //标准比例尺
134 //中心经纬度或仰角发生改变,必须重新计算经向和纬向的1度对应的球面距离
135 kmPerDegreeX = distanceOfSphere ( center.x, center.y, center.x+ 1.0 , center.y ) / cosineElevation;
136 kmPerDegreeY = distanceOfSphere ( center.x, center.y, center.x, center.y+ 1.0 ) / cosineElevation;
137 }
138
139 /**
140 * 功能:构造函数
141 * 参数:
142 * lon - 中心对应的经度坐标
143 * lat - 中心对应的纬度坐标
144 * x - 中心对应的屏幕位置x
145 * y - 中心对应的屏幕位置y
146 * sc - 缩放系数
147 * 返回值:
148 * 无
149 */
150 public Polar ( double lon, double lat, int x, int y, double sc ) {
151 reset ( lon, lat, x, y, sc, 0.0 ) ;
152 }
153
154 /**
155 * 功能:构造函数
156 * 参数:
157 * lon - 中心对应的经度坐标
158 * lat - 中心对应的纬度坐标
159 * x - 中心对应的屏幕位置x
160 * y - 中心对应的屏幕位置y
161 * sc - 缩放系数
162 * agl = 仰角
163 * 返回值:
164 * 无
165 */
166 public Polar ( double lon, double lat, int x, int y, double sc, double agl ) {
167 reset ( lon, lat, x, y, sc, agl ) ;
168 }
169
170 /**
171 * 功能:获得仰角
172 * 参数:
173 * 无
174 * 返回值:
175 * 仰角的度数
176 */
177 public double getElevation () {
178 return ( Math.toDegrees ( elevation )) ;
179 }
180
181 /**
182 * 功能:获得经纬度对应的屏幕像素坐标,与雷达仰角有关,主要用于体扫数据显示、底图叠加等。
183 * 参数:
184 * lon - 经度
185 * lat - 纬度
186 * 返回值:
187 * 对应的屏幕坐标
188 */
189 public Point getPosition ( double lon, double lat ) {
190 double disX = distanceOfSphere ( lon, center.y, center.x, center.y ) /cosineElevation;
191 double disY = distanceOfSphere ( center.x, lat, center.x, center.y ) /cosineElevation;
192 double x = ( lon>center.x? 1 :- 1 ) * ( disX*perKilometer*scale ) + place.x + 0.5 ;
193 double y = - ( lat>center.y? 1 :- 1 ) * ( disY*perKilometer*scale ) + place.y + 0.5 ;
194 return ( new Point (( int ) x, ( int ) y )) ;
195 }
196
197 /**
198 * 功能:获得极坐标对应的屏幕像素坐标,与雷达仰角无关,主要用于体扫数据显示、底图叠加等。
199 * 参数:
200 * radius - 极半径
201 * angle - 角度(以正北方向顺时针)
202 * 返回值:
203 * 对应的屏幕坐标
204 */
205
206 public Point getXY ( double radius, double angle ) {
207 int x = ( int )( 0.5 + radius * Math.sin ( Math.toRadians ( angle ))) ;
208 int y = ( int )( 0.5 + radius * Math.cos ( Math.toRadians ( angle ))) ;
209 return ( new Point ( place.x+x, place.y-y )) ;
210 }
211
212 /**
213 * 功能:获得屏幕像素点位置的极坐标半径,由于是输入参数是扫描平面上的值,故与雷达仰角无关。
214 * 参数:
215 * x - 水平坐标
216 * y - 垂直坐标
217 * 返回值:
218 * 与极坐标中心的距离,即极半径
219 */
220 public double getRadius ( int x, int y ) {
221 return ( Math.sqrt ( 1.0 * ( x-place.x ) * ( x-place.x ) + 1.0 * ( y-place.y ) * ( y-place.y ))) ;
222 }
223
224 /**
225 * 功能:获得经纬度位置的极坐标半径,与雷达仰角有关。
226 * 参数:
227 * lon - 经度坐标
228 * lat - 纬度坐标
229 * 返回值:
230 * 与极坐标中心的距离(象素点),即极半径
231 */
232 public double getRadius ( double lon, double lat ) {
233 Point pos = getPosition ( lon, lat ) ; //此函数已经考虑了仰角的影响
234 return ( getRadius ( pos.x, pos.y )) ;
235 }
236
237 /**
238 * 功能:获得屏幕像素点位置的极坐标角度(扫描平面与0度平面均相同),与雷达仰角无关。
239 * 参数:
240 * x - 水平坐标
241 * y - 垂直坐标
242 * 返回值:
243 * 角度值,自正北方向顺时针
244 */
245 public double getAngle ( int x, int y ) {
246 double agl = 0.0 ;
247 if ( x == place.x && y == place.y ) { //重合
248 agl = 0.0 ;
249 }
250 else if ( x == place.x ) {
251 agl = y > place.y ? 180.0 : 360.0 ;
252 }
253 else if ( y == place.y ) {
254 agl = x > place.x ? 90.0 : 270.0 ;
255 }
256 else {
257 agl = Math.toDegrees ( Math.atan ( 1.0 *Math.abs ( x-place.x ) /Math.abs ( y-place.y ))) ;
258 agl =
259 x > place.x && y < place.y ? agl : //直角坐标的第一象限
260 x < place.x && y < place.y ? 180.0 - agl : //直角坐标的第二象限
261 x < place.x && y > place.y ? 180.0 + agl : //直角坐标的第三象限
262 x > place.x && y > place.y ? 360.0 - agl : //直角坐标的第四象限
263 agl;
264 }
265 System.out.println ( agl ) ;
266 return ( agl ) ;
267 }
268
269 /**
270 * 功能:获得经纬度位置的极坐标角度(扫描平面与0度平面均相同),与雷达仰角无关。
271 * 参数:
272 * lon - 水平坐标
273 * lat - 垂直坐标
274 * 返回值:
275 * 角度值,自正北方向顺时针
276 */
277 public double getAngle ( double lon, double lat ) {
278 /*
279 //若通过获得屏幕坐标来计算角度,精度比较差,特别是在极坐标中心附近
280 Point p = getPosition(lon, lat);
281 return(getAngle(p.x, p.y);
282 */
283 double agl = 0.0 ;
284 if ( lon == center.x && lat == center.y ) { //重合
285 agl = 0.0 ;
286 }
287 else if ( lon == center.x ) {
288 agl = lat > center.y ? 360.0 : 180.0 ;
289 }
290 else if ( lat == center.y ) {
291 agl = lon > center.x ? 90.0 : 270.0 ;
292 }
293 else {
294 //注:由于经向和纬向的球面距离不等(华南,经向>纬向),故点(1,1)与中心点(0,0)的极角不等45度,而应是略大于45度
295 agl = Math.toDegrees ( Math.atan (( Math.abs ( lon-center.x ) *kmPerDegreeX ) / ( Math.abs ( lat-center.y ) *kmPerDegreeY ))) ;
296 agl =
297 lon > center.x && lat > center.y ? agl : //第一象限
298 lon < center.x && lat > center.y ? 180.0 - agl : //第二象限
299 lon < center.x && lat < center.y ? 180.0 + agl : //第三象限
300 lon > center.x && lat < center.y ? 360.0 - agl : //第四象限
301 agl;
302 }
303 return ( agl ) ;
304 }
305
306 /**
307 * 功能:
308 * 获得屏幕坐标对应的经纬度
309 * 参数:
310 * x - 屏幕水平坐标
311 * y - 屏幕垂直坐标
312 * 返回值:
313 * 对应的经纬度
314 */
315 public Point2D.Double getCoordinate ( int x, int y ) {
316 double lat = Math.toDegrees ( Math.toRadians ( center.y ) + ( place.y-y ) *cosineElevation/perKilometer/scale/Polar.RADIUS ) ;
317 double disX0 = distanceOfSphere ( center.x, lat, center.x+ 1.0 , lat ) ; //0度平面上1经度的球面距离
318 double disX = disX0 / cosineElevation; //扫描平面上1经度的距离
319 double perDegreeX = disX * perKilometer * scale; //扫描平面上1经度的对应的像素点数
320 double lon = center.x + ( x - place.x ) / perDegreeX;
321 return ( new Point2D.Double ( lon, lat )) ;
322 }
323
324 /**
325 * 功能:
326 * 画经线、纬线
327 * 参数:
328 * g - 图形设备
329 * f - 字体
330 * c - 画线颜色
331 * inc_lon - 经线间隔//未使用
332 * inc_lat - 纬线间隔//未使用
333 * 返回值:
334 * 无
335 */
336 public void drawGridLine ( Graphics2D g, Font f, Color c, int inc_lon, int inc_lat ) {
337 Color saveColor = g.getColor () ;
338 Font saveFont = g.getFont () ;
339
340 //以下两行改进线条的锯齿
341 //RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
342 //g.setRenderingHints(renderHints);
343
344 // g.setColor(Color.black);//背景色
345 // g.fillRect(c.x-(int)(z*240), c.y-(int)(z*240), (int)(z*240*2), (int)(z*240*2));
346
347 g.setColor ( c ) ; //雷达图形区域的边框颜色
348 g.drawRect (( int )( 0.5 +place.x-scale* 240 ) , ( int )( 0.5 +place.y-scale* 240 ) , ( int )( 0.5 +scale* 240 * 2 ) , ( int )( 0.5 +scale* 240 * 2 )) ;
349
350 //画极径
351 Point pos1, pos2;
352 for ( double i= 0.0 ;i< 180.0 ;i=i+ 30.0 ) {
353 pos1 = getXY ( scale* 240.0 , 0.0 +i ) ;
354 pos2 = getXY ( scale* 240.0 , 180.0 +i ) ;
355 g.drawLine ( pos1.x, pos1.y, pos2.x, pos2.y ) ;
356 }
357
358 //画极圈
359 for ( int i= 50 ;i<= 200 ;i=i+ 50 ) { //每50公里画一个圈
360 g.drawArc (( int )( 0.5 +place.x-scale*i ) , ( int )( 0.5 +place.y-scale*i ) , ( int )( 0.5 +scale*i* 2 ) , ( int )( 0.5 +scale*i* 2 ) , 0 , 360 ) ;
361 }
362 g.drawArc (( int )( 0.5 +place.x-scale* 240 ) , ( int )( 0.5 +place.y-scale* 240 ) , ( int )( 0.5 +scale* 240 * 2 ) , ( int )( 0.5 +scale* 240 * 2 ) , 0 , 360 ) ; //外圈240公里
363
364 g.setFont ( saveFont ) ;
365 g.setColor ( saveColor ) ;
366 }
367 }