1;+ 2; NAME: 3; MAP_GRID 4; 5; PURPOSE: 6; Draws graticules 7; 8; KEYWORD PARAMETERS: 9; 10; 11; BOX_AXES: 12; CHARSIZE: 13; COLOR: 14;FILL_HORIZON: 15; HORIZON: 16; INCREMENT: sets spacing between graticle points. 17; GLINESTYLE: Line style of grid lines (passed to "plots") 18; GLINETHICK: idem for thickness 19; LABEL: LABEL n puts a label every n graticle lines, startin 20; with min lat/lon unless LATS or LONS is a single value, this 21; being the starting point. 22; LATALIGN: Label alignment value form 0.0 to 1.0 23; LATDEL: latitude spacing of lines 24; LATS: supercedes the LATDEL mechanism by providing a series of latitudes 25; LATLAB: Which longitude to place latitude labels, default being 26; the center longitude on the map. 27; LATNAMES: names used instead of normal latitude labels. Wizard only. 28; LONALIGN: see LATALIGN, but for longitudes 29; LONDEL: see LATDEL 30; LONLAB: see LATLAB 31; LONS: see LATS 32; LONNAMES: see LATNAMES 33; 34; MAP_STRUCTURE: use a map structure and not !MAP. 35; 36; NO_GRID: only labels, no gridlines. 37; 38;ORIENTATION: of the labels (reverse direction from XYOUTS) 39; 40; T3D: 41; ZVALUE: 42; 43;- 44 45function map_point_transformable,x,y 46 xy=map_proj_forward(x,y) 47 return, finite(xy[0]) and finite(xy[1]) 48end 49 50function map_grid_toformat,s,f 51 if (reverse(size(s)))[1] eq 7 then return,s else return,strtrim(string(s, FORMAT=f),2) 52end 53 54;------------------------------------------------------------------------- 55function compute_map_grid_increment, range 56 COMPILE_OPT hidden, IDL2 57 if range eq 0 then return, 45. 58 ipow = 0 59 t = abs(range) < 450. 60 while t lt 5 do begin 61 t = t * 10 62 ipow = ipow +1 63 endwhile 64 increments = [ 1., 2., 4., 5., 10., 15., 30., 45.] 65 i = 0 66 while t gt (increments[i] * 10) do i = i + 1 67 t = increments[i] / 10^ipow 68 return, range ge 0 ? t : -t 69end 70 71 72;------------------------------------------------------------------------- 73; ancillary function to find where the line [A,B] (in device coords) 74; intersects the longitude (if type=0) or latitude (type=1) 75; 'val'. when this is impossible, returns Nan. 76function find_grid_intersection, A, B, type, val, MAP_STRUCTURE=mapStruct 77 COMPILE_OPT hidden, IDL2 78 hasMap = N_TAGS(mapStruct) gt 0 79 startPixel = CONVERT_COORD(A, /DEVICE, /TO_DATA) 80 endPixel = CONVERT_COORD(B, /DEVICE, /TO_DATA) 81 if (hasMap) then begin ; we want lons / lats. 82 startPixel = MAP_PROJ_INVERSE(startPixel[0], startPixel[1], MAP_STRUCTURE=mapStruct) 83 endPixel = MAP_PROJ_INVERSE(endPixel[0], endPixel[1], MAP_STRUCTURE=mapStruct) 84 endif 85 startPixel = startPixel[type] 86 endPixel = endPixel[type] 87 if ~finite(startPixel) or ~finite(endPixel) or (endPixel eq startPixel) then return, !values.d_nan 88 if (type eq 0) and (startPixel gt endPixel) then if val ge 0 then endPixel += 360. else startPixel -= 360. 89 x = (val - startPixel) / (endPixel-startPixel) 90 if (x gt 1.0) or (x lt 0.0) then return, !values.f_nan 91 debut = 0.0 & fin = 1.0 & range = B - A 92 while abs(fin-debut) gt 1e-5 do begin 93 x = (debut + fin) / 2. & intersect = A + x * range & point = CONVERT_COORD(intersect, /DEVICE, /TO_DATA) 94 if (hasMap) then point = MAP_PROJ_INVERSE(point[0], point[1], MAP_STRUCTURE=mapStruct) 95 point = point[type] 96 if (~FINITE(point)) then $ 97 return, !values.f_nan 98 if (type eq 0) then if point lt startPixel then point += 360. else if point gt endPixel then point -= 360. 99 if (val-startPixel) * (val - point) gt 0.0 then begin 100 debut = x 101 startPixel = point 102 endif else fin = x 103 endwhile 104 return, intersect[type] 105end 106 107 108;------------------------------------------------------------------------- 109PRO map_grid, LABEL=labelSeparation, LATDEL = lat_separation, NO_GRID=nogrid, $ 110 LONDEL=lon_separation, GLINESTYLE=ourLineStyle, GLINETHICK=ourLineThick,$ 111 LONLAB=longitudeOfLabels, LATLAB=latitudeOfLabels, LONALIGN=lonalign, $ 112 LATALIGN=latalign, LONNAMES=lonstrings, LATNAMES=latstrings, $ 113 LATS=passed_lats, LONS=passed_lons, $ 114 COLOR=gridColor, CHARSIZE=charsize, ORIENTATION=orientation, $ 115; MAP_HORIZON keywords: 116 HORIZON=horizon, E_HORIZON=ehorizon, FILL_HORIZON=fill_horizon, $ 117 INCREMENT=increment, CLIP_TEXT=clip_text, BOX_AXES=box_axes, $ 118 MAP_STRUCTURE=passedMap, ZVALUE=zvalue, T3D=t3d, _EXTRA=extra, $ 119 WHOLE_MAP=dummy 120 compile_opt idl2 121 ON_ERROR, 2 122 123; first, evacuate some problems 124 125 has_passed_map = n_tags(passedMap) gt 0 126 127 if ((!x.type ne 3) and ~has_passed_map) THEN message, 'Current ploting device must have mapping coordinates' 128 129 dobox=keyword_set(box_axes) 130 131; graphic values to remember: 132 d_y_ch_size = !d.y_ch_size 133 if n_elements(charsize) then d_y_ch_size = d_y_ch_size * charsize 134 box_thick = (dobox)?box_axes*0.1*!d.y_px_cm : 0; thickness of box_axe in device units 135 xpixels = !x.window * !d.x_size 136 ypixels = !y.window * !d.y_size 137 138 139 if n_elements(gridColor) eq 0 then gridColor = !p.color ;Default color 140 if n_elements(t3d) le 0 then t3d = 0 141 if n_elements(zvalue) eq 0 then zvalue = 0. 142 if n_elements(charsize) eq 0 then charsize = !p.charsize 143 if charsize le 0.0 then charsize = 1.0 144 145 ; graphic passable keywords 146 map_struct_append, egraphics, "COLOR", gridColor 147 map_struct_append, egraphics, "T3D", t3d 148 map_struct_append, egraphics, "ZVALUE", zvalue 149 150; a projection exist. 151; if Label = n, then Labels are added every n gridlines 152; If box_axes is set, and LABEL isn't explicitly specified, set labelSeparation. 153; 154 nuberOfLabels = (n_elements(labelseparation) gt 0) ? fix(abs(labelseparation[0])) : dobox 155 noclip = (n_elements(clip_text) gt 0) ? ~keyword_set(clip_text) : 0 ;1 to clip text within the map area, 0 to not. 156 drawGrid = ~keyword_set(nogrid) ; we want grid, not just labels 157 158; we need to process these options here: 159 if n_elements(gridColor) ne 0 then map_struct_append, extra, 'COLOR',gridColor 160 if n_elements(charsize) ne 0 then map_struct_append, extra,'CHARSIZE', charsize 161 if n_elements(ourLineThick) ne 0 then map_struct_append, extra,'THICK', ourLineThick 162 map_struct_append, extra,'LINESTYLE', (n_elements(ourLineStyle) gt 0)?ourLineStyle:1 ; default value is DOTTED LINE. 163 if n_elements(orientation) ne 0 and ~dobox then map_struct_append, extra,'ORIENTATION', -1 * orientation ;Orientation is reversed & conflicts w/box_axes 164 165; gridlines can be, by order of preference: 166; 1) set by lats=[...] lons=[...] 167; 2) 1 lats and/or lon value, the center, and then 168; 3) each line is computed from this center using default or not of lat_separation/londel. 169; 170; if latnames is present, lats must be also. 171 doMyLons = n_elements(passed_lons) gt 0 172 doMyLats = n_elements(passed_lats) gt 0 173 if n_elements(latstrings) gt 0 and ~doMyLats then message,'LATNAMES keyword cannot be used without LATS keyword also defined.' 174 if n_elements(lonstrings) gt 0 and ~doMyLons then message,'LONNAMES keyword cannot be used without LONS keyword also defined.' 175 176 myMap = has_passed_map ? passedMap : !MAP 177 if doMyLats then begin ;Lats directly specified? 178 minlat = passed_lats[0] 179 maxlat = passed_lats[-1] 180 endif else if myMap.ll_box[0] ne myMap.ll_box[2] then begin 181 minlat = myMap.ll_box[0] 182 maxlat = myMap.ll_box[2] 183 endif else begin 184 minlat = -90 185 maxlat = 90 186 endelse 187 188 if doMyLons then begin 189 minlon = passed_lons[0] 190 maxlon = passed_lons[-1] 191 endif else if (myMap.ll_box[1] ne myMap.ll_box[3]) and (maxlat lt 90.) and (minlat gt -90.) then begin 192 minlon = myMap.ll_box[1] 193 maxlon = myMap.ll_box[3] 194 endif else begin 195 minlon = -180 196 maxlon = 180 197 endelse 198 199 if maxlon le minlon then maxlon = maxlon + 360. 200 201;Grid spacing 202 if n_elements(lat_separation) gt 0 then latdelta=lat_separation[0] else begin 203 lat_separation = compute_map_grid_increment(maxlat - minlat) 204 latdelta = 1 205 endelse 206 207 if n_elements(lon_separation) gt 0 then londelta = lon_separation[0] else begin 208 lon_separation = compute_map_grid_increment(maxlon - minlon) 209 londelta = 1 210 endelse 211 212 if abs(maxlat - minlat) gt 5. and latdelta ge 1 then begin ; IF the deltas are smaller than 1 degree, do not convert to integers 213 minlat = float(floor(minlat)) 214 maxlat = ceil(maxlat) 215 endif 216 217 if abs(maxlon - minlon) gt 5 and londelta ge 1 THEN BEGIN ;idem 218 minlon = float(floor(minlon)) 219 maxlon = ceil(maxlon) 220 endif 221; labels 222 if n_elements(latitudeOfLabels) eq 0 then latitudeOfLabels = (minlat + maxlat)/2 223 if n_elements(longitudeOfLabels) eq 0 then longitudeOfLabels = (minlon +maxlon)/2 224 if n_elements(latalign) eq 0 then latalign = .5 ;center 225 if n_elements(lonalign) eq 0 then lonalign = .5 ;center 226 227 if (has_passed_map) then map_proj_info, iproj, cylindrical=is_cyl, map_struct=passedMap else map_proj_info, iproj, cylindrical=is_cyl, /current 228 229 if keyword_set(increment) then step = increment else step = 4 < (maxlat - minlat)/10. 230 231 latlistsize = long(float((maxlat-minlat)) / float(step) + 1.0) 232 233 latlist = (float(maxlat-minlat) / (latlistsize-1.)) * findgen(latlistsize) + minlat > (-90) < 90 ; Avoid roundoff errors 234 235 if is_cyl and myMap.p0lat eq 0 then begin 236 if latlist[0] eq -90 then latlist[0] = -89.98 237 if latlist[latlistsize-1] eq 90 then latlist[latlistsize-1] = 89.98 238 endif 239 240 lonStep = 4 < (maxlon - minlon)/10. ; max 4 degrees! 241 lonListSize = (maxlon-minlon)/lonStep + 1 242 lonlist = findgen(lonListSize) * lonStep + minlon 243 if (lonlist[lonlistsize-1] ne maxlon) then lonlist = [lonlist, maxlon] 244; 245 if n_elements(passed_lats) eq 0 then begin 246 lat0 = minlat - (minlat mod float(lat_separation)) ;1st lat for grid 247 n_passed_lats = 1 + fix((maxlat-lat0)/float(lat_separation)) 248 latitude_list = lat0 + findgen(n_passed_lats)*lat_separation 249 endif else if n_elements(passed_lats) eq 1 then begin 250 i0 = ceil((minlat - passed_lats[0]) / float(lat_separation)) ;First tick 251 i1 = floor((maxlat - passed_lats[0]) / float(lat_separation)) ;Last tick 252 n_passed_lats = i1 - i0 + 1 > 1 253 latitude_list = (findgen(n_passed_lats) + i0) * lat_separation + passed_lats[0] 254 endif else begin 255 n_passed_lats=n_elements(passed_lats) 256 latitude_list=passed_lats 257 endelse 258; 259 if n_elements(passed_lons) eq 0 then begin 260 n_passed_lons = 1+fix((maxlon-minlon) / lon_separation) 261 longitude_list = minlon - (minlon mod lon_separation) + findgen(n_passed_lons) * lon_separation 262 endif else if n_elements(passed_lons) eq 1 then begin 263 i0 = ceil((minlon - passed_lons[0]) / float(lon_separation)) 264 i1 = floor((maxlon - passed_lons[0]) / float(lon_separation)) 265 n_passed_lons = i1 - i0 + 1 > 1 266 longitude_list = (findgen(n_passed_lons) + i0) * lon_separation + passed_lons[0] 267 endif else begin 268 n_passed_lons=n_elements(passed_lons) 269 longitude_list=passed_lons 270 endelse 271; 272 doLabelLon = bytarr(n_passed_lons) 273 doLabelLat = bytarr(n_passed_lats) 274 if nuberOfLabels gt 0 then begin 275 if n_elements(passed_lats) eq 1 then begin 276 w=where(latitude_list eq passed_lats[0], count) & if count gt 0 then for i=(w[0] mod nuberOfLabels), n_passed_lats-1, nuberOfLabels do doLabelLat[i] = 1 277 endif else for i=0, n_passed_lats-1, nuberOfLabels do doLabelLat[i] = 1 278 279 if n_elements(passed_lons) eq 1 then begin 280 w=where(longitude_list eq passed_lons[0], count) & if count gt 0 then for i=(w[0] mod nuberOfLabels), n_passed_lons-1, nuberOfLabels do doLabelLon[i] = 1 281 endif else for i=0, n_passed_lons-1, nuberOfLabels do doLabelLon[i] = 1 282 endif 283; put lat lons texts into convenient array 284 n = n_passed_lons > n_passed_lats & textArrayForLonLat = strarr(n, 2) 285 286 if dobox then begin 287 fudgefact = [0.01,-0.01] 288 xboxpix = (myMap.uv_box[[0,2]] * !x.s[1] + !x.s[0]) * !d.x_size + fudgefact 289 yboxpix = (myMap.uv_box[[1,3]] * !y.s[1] + !y.s[0]) * !d.y_size + fudgefact 290 291 292 if n_elements(gridColor) eq 0 then bcolor = !p.color $ ;Box color 293 else bcolor = gridColor 294 295 xp = xpixels[0] - [0,box_thick, box_thick,0] ;X & Y polygon coords for outer box 296 yp = ypixels[0] - [0,0,box_thick,box_thick] 297 ;Draw the outline of the box 298 plots, xpixels[[0,1,1,0,0]], ypixels[[0,0,1,1,0]], /DEVICE, COLOR=bcolor 299 plots, xpixels[[0,1,1,0,0]]+[-box_thick, box_thick, box_thick, -box_thick, -box_thick], $ 300 ypixels[[0,0,1,1,0]]+[-box_thick, -box_thick, box_thick, box_thick, -box_thick], /DEVICE, COLOR=bcolor 301 302 ychar = [ypixels[0]-box_thick-d_y_ch_size, ypixels[1]+box_thick+d_y_ch_size/4.] 303 xchar = [xpixels[0] - box_thick - d_y_ch_size/4., xpixels[1]+box_thick+d_y_ch_size/4.] 304 305 boxpos = replicate(!values.f_nan, n, 2,2) 306 endif 307 308; map_horizon? first, because of possibility to be filled... 309 if keyword_set(horizon) or keyword_set(ehorizon) or keyword_set(fill_horizon) then begin 310 merge_structs_mapset, ehorizon, egraphics ;Add common graphics keywords 311 if keyword_set(fill_horizon) then map_struct_append, ehorizon, "FILL", fill_horizon 312 MAP_HORIZON, _EXTRA=ehorizon 313 endif 314 315; arrange longitude_list btw. -180 and 180 316 map_adjlon,longitude_list 317 318; 319; parallels (curlat) and labels at latlab 320; 321 322 FOR i=0,n_passed_lats-1 DO BEGIN 323 324 curlat=latitude_list[i] ; no fudge for lats. 325 326 fmt = (curlat ne long(curlat)) ? '(f7.2)' : '(i4)' 327 328 IF (drawGrid and (abs(curlat) ne 90)) then begin 329 y = REPLICATE(curlat, N_ELEMENTS(lonlist)) 330 x = lonlist 331 if (has_passed_map) then begin 332 xy = map_proj_forward(x,y, MAP_STRUCTURE=passedMap, POLYLINES=polyconn) 333 n = N_ELEMENTS(xy)/2 334 if (n lt 2) then break ; not drawable 335 index = 0L 336 while (index lt n) do begin 337 ipoly = polyconn[index + 1 : index + polyconn[index]] 338 plots, xy[0,ipoly], xy[1,ipoly], zvalue, NOCLIP=0, _EXTRA=extra 339 index = index + polyconn[index] + 1 340 endwhile 341 endif else begin 342 PLOTS, x, y, NOCLIP=0, _EXTRA=extra 343 endelse 344 endif 345 346 IF doLabelLat[i] THEN BEGIN 347 IF i lt n_elements(latstrings) then ls=map_grid_toformat(latstrings[i],fmt) else ls=strtrim(string(curlat, format=fmt),2) 348 textArrayForLonLat[i,1] = ls 349 if ~dobox then begin 350 xy = 0; only one element if failed. 351 if (has_passed_map) then begin 352 uv = MAP_PROJ_FORWARD(LongitudeOfLabels, curlat, MAP_STRUCTURE=passedMap) 353 if (FINITE(uv[0]) and FINITE(uv[1])) then xy = uv[0:1] 354 endif else begin 355 if (noclip eq 1) or map_point_transformable(LongitudeOfLabels, curlat) then xy = [LongitudeOfLabels, curlat] 356 endelse 357 if (N_ELEMENTS(xy) eq 2) then XYOUTS, xy[0], xy[1], ls, ALIGNMENT=latalign, NOCLIP=noclip, _EXTRA=extra 358 endif 359 ENDIF 360 361 362 if dobox then begin 363 dx = (xpixels[1] - xpixels[0]) * 0.01 364 for j=0,1 do begin 365 k = 0 366 while ~finite(boxpos[i,j,1]) and abs(k) lt 3 do begin 367 boxpos[i, j, 1] = find_grid_intersection( [xpixels[j]+dx*k, yboxpix[0]], [xpixels[j]+dx*k, yboxpix[1]], 1, curlat, map_structure=passedMap) 368 k = k + (j ? -1 : 1) 369 endwhile 370 endfor 371 endif 372 373 endfor 374 375; draw meridians (curlon) and labels at 'lonlab' 376 FOR i=0,n_passed_lons-1 DO BEGIN 377 lon=longitude_list[i] & fudged_lon=lon 378 fmt = (lon ne long(lon)) ? '(f7.2)' : '(i4)' ; future format 379 if is_cyl then begin 380 fudgefact = fudged_lon - myMap.p0lon & map_adjlon,fudgefact 381 if fudgefact eq -180 then fudged_lon += 1.0e-5 else if fudgefact eq 180 then fudged_lon -= 1.0e-5 382 endif 383 384 IF (drawGrid) THEN begin ; we use fudged_lon to avoid split problems. 385 x = REPLICATE(fudged_lon, N_ELEMENTS(latlist)) 386 y = latlist 387 if (has_passed_map) then begin 388 xy = map_proj_forward(x,y, MAP_STRUCTURE=passedMap, POLYLINES=polyconn) 389 n = N_ELEMENTS(xy)/2 390 if (n lt 2) then break ; not drawable 391 index = 0L 392 while (index lt n) do begin 393 ipoly = polyconn[index + 1 : index + polyconn[index]] 394 plots, xy[0,ipoly], xy[1,ipoly], zvalue, NOCLIP=0, _EXTRA=extra 395 index = index + polyconn[index] + 1 396 endwhile 397 endif else begin 398 PLOTS, x, y, NOCLIP=0, _EXTRA=extra 399 endelse 400 endif 401 402 IF doLabelLon[i] THEN BEGIN 403 IF i lt n_elements(lonstrings) then ls=map_grid_toformat(lonstrings[i],fmt) else ls=strtrim(string(lon, format=fmt),2) 404 405 textArrayForLonLat[i,0] = ls 406 if ~dobox then begin 407 xy = 0; only one element if failed. 408 if (has_passed_map) then begin 409 uv = MAP_PROJ_FORWARD(fudged_lon, LatitudeOfLabels, MAP_STRUCTURE=passedMap) 410 if (FINITE(uv[0]) and FINITE(uv[1])) then xy = uv[0:1] 411 endif else begin 412 if (noclip eq 1) or map_point_transformable(fudged_lon, LatitudeOfLabels) then xy = [fudged_lon, LatitudeOfLabels] 413 endelse 414 if (N_ELEMENTS(xy) eq 2) then XYOUTS, xy[0], xy[1], ls, ALIGNMENT=lonalign, NOCLIP=noclip, _EXTRA=extra 415 endif 416 417 ENDIF 418 if dobox then begin 419 dy = (ypixels[1] - ypixels[0]) * 0.01 420 for j=0,1 do begin 421 k = 0 422 while ~finite(boxpos[i,j,0]) and abs(k) lt 3 do begin 423 boxpos[i, j, 0] = find_grid_intersection([xboxpix[0], ypixels[j]+k*dy],[xboxpix[1], ypixels[j]+k*dy], 0, lon, map_structure=passedMap) 424 k = k + (j ? -1 : 1) 425 endwhile 426 endfor 427 endif 428 endfor 429 430 if dobox then begin 431 for axisnum=0,1 do begin 432 for axisside=0,1 do begin 433 val = boxpos[*,axisside,axisnum] 434 w = where(finite(val), count) & if (count eq 0) then continue 435 dy = axisnum eq 1 436 val = val[w] 437 sorted = sort(val) 438 val = val[sorted] 439 labeltxt = (textArrayForLonLat[w,axisnum])[sorted] 440 val0 = ([xpixels[0], ypixels[0]])[axisnum] 441 xxp = xp + axisside * (xpixels[1]-xpixels[0] + box_thick) 442 yyp = yp + axisside * (ypixels[1]-ypixels[0] + box_thick) 443 xychar = [xchar[axisside], ychar[axisside]] 444 for ii=0, count-1 do begin 445 z = val[ii] 446 if axisnum eq 0 then xxp = (ii eq (count-1) and (ii and 1) and ~labeltxt[ii]) ? [val0, xpixels[1], xpixels[1], val0] : [val0, z, z, val0] else yyp = [val0, val0, z, z] 447 if (ii and 1) then polyfill, xxp, yyp, /device, color=bcolor 448 xychar[axisnum] = z 449 if strlen(labeltxt[ii]) gt 0 then xyouts, xychar[0], xychar[1], labeltxt[ii], orientation=dy * (90-180*axisside), align=0.5, clip=0, /device, _extra=extra 450 val0 = z 451 endfor 452 if ii and 1 then begin ; finish filling 453 if axisnum eq 0 then xxp = [val0, xpixels[1], xpixels[1], val0] else yyp = [val0, val0, ypixels[1], ypixels[1]] 454 polyfill, xxp, yyp, /device, color=bcolor 455 endif 456 endfor 457 endfor 458 endif 459end 460