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