function [filename,out,commands] = gmt_plot(in,field) % GMT_PLOT plotting interface for GMT (Generic Mapping Tools) % % The function is basically a wrapper that calls GMT shell commands using easy % to use matlab arguments. % Try test_gmt for some examples on how to use the function. % Preferably, the data should be CENTERED if the data is gridded. Plotting % gridded data is faster than plotting ungridded data. This GMT wrapper supports % GMT 5. % % OUT filename The filename of the figure. % % IN in A structure with input data and options. See below for % a detailed description of the structure. % % OPT field The string name of the structure element that contains % the data to be plotted. If no field is given, 'data' % is assumed. % % Easy example: If you just want to plot some data.. % in.somefield = data2plot % in.lon = longitudes % in.lat = latitudes % % file = gmt_plot(in,'somefield'); % % see STRUCTURE CONTENTS below for the rest of the available arguments % % VIEWING THE PLOT: % The easiest thing to do is to define a viewer in your startup file. % % Add something like the following to your startup file') % gmtlab( 'OUTDIR', '/name/a/favorite/directory' ) %% The default directory to put plots % gmtlab( 'PSVIEWER','gv'); %% set viewer for .ps files % gmtlab( 'PDFVIEWER','xpdf -z width'); %% set viewer for .pdf files % --> gmtlab( 'OPEN_COMMAND','gnome-open'); %% set a general open command <-- This one is enough % gmtlab( 'VERBOSITY',1); %% This outputs stdout on the screen. % % Structure contents: % % MANDATORY INPUT % % 1) in.(field) % the data (default is field='data' i.e. in.data) % 2) in.lat % the corresponding latitudes % 3) in.lon % the corresponding longitudes % % If your data is ungridded the dimensions of the manditory input % (lat,lon.data) must be the same. % i.e isequal(size(in.lat),size(in.lon),size(in.data)) % % % NOTE: 1) If your data is UNGRIDDED I recommend setting % in.nearneighbor.resolution = Deg (something suitable). % 2) gmt_plot automatically detects if the data is gridded or not % % % HOW TO READ THIS HELP % in.variable in = expect input type % ex = Explanation/Example % de = Default value/behavior % % NOTE: If "def" is missing for a variable it means the variable is not used by default % % types: %s=character or string % %f=numeric/logical % {}=cell %-------------------------------------------------------------------------- % % OPTIONS % % ------------ % in.add_offset in: %f % ex: The offset you want to automatically apply to the % provided data. This is often provided % by convention in the attributes of a variable % (from netcdf). See also scale_factor and % fill_value % ------------ % in.cptfile in: %s % ex: Path to a colortable (*.cpt) % ------------ % in.ctable See below how to set the color table (default=rainbow) % ------------ % in.color_background in: %s % ex: Set the background color (values less than range) % def: '0/0/0' ('black') % ------------ % in.color_foreground in: %s % ex: Set the foreground color (values greater than range) % def: '255/255/255' ('white') % ------------ % in.color_nan in: %s % ex: Set the color of NaNs % def: '125/125/125' ('grey') % ------------ % in.colorrange See below how to make your own colortables % ------------ % in.contourline See below how to draw contour lines on the map % ------------ % in.datarange in: [%f, %f] % ex: Min and max range of data values to display. If % you only provide a scalar, it is treated as the minimum % value in the range % ------------ % in.display in: %f % ex: If false, file created but not displayed % def: true % ------------ % in.filename in: %s % ex: 'yourfilename', or 'yourfilename.jpg' % def: Generated from title. The filetype is determined by the file % ending given by your filename, e.g., any of bla.{pdf,jpg,eps,tif,png,ps} % ------------ % in.fill_value in: %f % ex: The fill value to NaN-out. This is often provided % by convention in the attributes of a variable % (from netcdf). See also scale_factor and % add_offset % ------------ % in.figuretype in: %s % ex: 'eps','pdf','BMP','jpg','png','PPM' (not recommended), or 'tif'. % If the figure type is included in the filename, % e.g., file.png, or file.jpg, then that file type is % used, however defining in.figuretype will % override this. % def: 'pdf' % ------------ % in.fontsize_annot_primary % in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: The size of the font, e.g., '2c'=2cm for e.g., the axis. % Setting this will also change the size of the % fonts of the annotations for the legend. If you % want a different size for the legend % annotations, set in.legend.fontsize. % def: '14p' % ------------ % in.font_annot_primary % in: %s or %f % ex: There are 35 fonts to choose from. Either provide a % number 0-34 or a string (case sensitive) of the % font to use. E.g., 'Helvetica' (0), 'Times-Roman' (4), % 'Courier' (8), etc. See "gmtdefault" man page for the % whole list. You can even setup other fonts % (see also GMT fonts % ------------- % in.fontsize_annot_secondary % in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: The size of the font, e.g., '2c'=2cm for e.g., the axis. % Setting this will also change the size of the % fonts of the annotations for the legend. If you % want a different size for the legend % annotations, set in.legend.fontsize. % def: '14p' % ------------ % in.font_annot_secondary % in: %s or %f % ex: There are 35 fonts to choose from. Either provide a % number 0-34 or a string (case sensitive) of the % font to use. E.g., 'Helvetica' (0), 'Times-Roman' (4), % 'Courier' (8), etc. % def: 'Helvetica' ('0') % ------------- % in.font_title in: %s or %f % ex: There are 35 fonts to choose from. Either provide a % number 1-35 or a string (case sensitive) of the % font to use. E.g., 'Helvetica' (0), 'Times-Roman' (4), % 'Courier' (8), etc. See "gmtdefault" man page for the % whole list % def: 'Helvetica' ('0') % -------------- % in.fontsize_title in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: Title size % def: '36p' (points) % -------------- % in.func in: function handle % ex: Apply this function to the data field. e.g. @(x)(nanmean(x(:,:,:,2),3)) % def: none % -------------- % in.gmtset in: {%s,%s,...} % ex: Cell with one or more gmtset commands. % E.g. in.gmtset={'gmt set FORMAT_FLOAT_OUT %3.1e','gmt set ...'} % ------------ % in.grdimage in: %s % ex: 'gmt grdimage OPT > filename.ps', explicitly sets % the grdimage GMT command directly. % ------------ % in.keep_files in: %f % ex: If you don't want to delete intermediate files. % If you are really after the shell script that make % the plot see in.makeScriptAndStop % def: false % ------------ % in.map_title_offset % in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: Moves the title in y-dir on the page % def: '0c' (cm) % ------------ % in.makecpt in: %s % ex: 'gmt makecpt OPT > filename.ps', explicitly sets the % makecpt GMT command directly. % ------------ % in.makeScriptAndStop % in: %f % ex: For the developer and those who want the shell script % generated by this program % def: false % ------------- % in.mask in: function handle % ex: @(x)(x<0); This masks all values less 0 with NaN % def: no mask by default % ------------ % in.nearneighbor: See below on nearneigbour % ------------ % in.nlevels in: %d % ex: Refers to the number of contour levels (converted to % stepsize using datarange) % def: best fit for nice tickvalues % ------------ % in.force_nlevels in: (logical) % ex: If set to true, keep nlevels even if it % exceeds the number of unique values. If % set to false, nlevels is reduced to the % number of unique values if it exceeds it. % def: false % ------------ % in.locations See below how to plot locations on the map % ------------ % in.nodata in: %f % ex: If you only want a map (compatible with all options) % def: false % ------------ % in.outdir in: %s % ex: 'name/a/directory' % def: gmtlab('OUTDIR') ('.' if not set) % ------------ % in.plotPlacement in: %s % ex: String of global plotPlacements (see "man grdimage" for info) % def: '-Ya5 -Xa5' % ------------ % in.proj See below on map projections/size % ------------ % in.proj_length_unit % in: %s % ex: You can explicitly specify the unit used for % distances and lengths by appending c (cm), i (inch), m % (meter), or p (points). When no unit is indicated the % value will be assumed to be in the unit set by % proj_length_unit. The default unit to use for all commands % if none is given. This used to be called % measure_unit in earlier versions but is now % adapted to gmt5 conventions % % def: 'c' % ------------ % in.psbasemap See below on psbasemap % ------------ % in.pscoast See below on pscoast % ------------ % in.pspoly See below on pspoly how to draw shapes % ------------ % in.pstext See below on pstext write on the map % ------------ % in.region in: %s % ex: '-180/180/-90/90'=global range, defined from % sprintf('%f/%f/%f/%f',lon1,lon2,lat1,lat2) % def: determined by input lat/lon range % ------------ % in.scale_factor in: %f % ex: The scale factor you want to automatically apply to the % provided data. This is often provided % by convention in the attributes of a variable % (from netcdf). See also add_offset and % fill_value % ------------ % in.stepsize in: %f % ex: The stepsize between data values. This overrides % nlevels (-T/min/max/stepsize in makecpt) % def: determined by nlevels % ------------ % % in.tickval in: %f % ex: Vector of values to be used for ticks and data interval % def: Determined by datarange and stepsize % ------------ % in.title in: %s % ex: Title of plot % def '' % ------------ % in.treatAsGridded in: %f % ex: 1; This is a patch. It allows you to treat gridded data as ungridded. % This can be useful if you think the % internal interpolation for the plot projection is % causing weird side effects (such as near NaN values) % def: 0 % ------------ % in.unit in: %s % ex: String displayed on the y-axis of a legend % def: '' % ------------ % in.xunit in: %s % ex: String displayed on the x-axis of a legend % def: '' % ------------ % % NOTE: For SPECIAL CHARACTERS in a string (such as title) refer to the % GMT cookbook (GMT Cookbook) % % ====================== % Structure options % ====================== % %--------------------------- % % COLORRANGE % % NOTE: For your own CUSTOM COLORTABLE use in.colorrange.colors % (see below). Assign a color to a relative value, e.g between 0-1, where % 0 is for the minimum of the datarange and 1 is for the color of the % maximum datarange. For example, % in.colorrange.colors = {{0,'250/0/0''},{.3,'0/0/0'},{0.5,'0/255/0'},{1,'0/0/255'}}, % makes a colortable that goes from red to black to green to blue. % Naturally you can also use the 'contour values' directly as long as you % assign all levels a color. % % ---------------------- % in.colorrange. | STRUCTURE with one or more of the following fields: % % colors in: {{%d,%s},{%d,%s},etc.} %d is the data value, %s is the color % ex: {20,'255/0/0'} (red in RGB) % ------------ % color_model in: %s % ex: 'RGB', 'HSV' % def: 'RGB' % ------------ % %------------------------------------------------------------------------------------ % % COLOR TABLE % % ---- string -------- % in.ctable in: %s % ex: See GMT color palettes % def: 'rainbow' % % NOTE: 'mypolar' is a nice colortable for difference % plots, and there are two additional optional % arguments for this color table: % ----- struct --------- % % in.ctable % ------------ % name in: %s % ex: 'rainbow' | 'mypolar' | etc % ------------ % nwhite: in: %d % ex: The number of white contours around % a reference value % def: determined by number of levels % ------------ % reference: in: %d % ex: Where to center the white color % def: mean(data(:)) % ------------ % % Note: Recommend set in.reference = 0 for % difference plots, but any reference % will work. % ------------ % reverse in: %d % ex: Reverse the colortable if set to 1. % % Note: Currently working with all built-in colortables % as well as the typhon colortables. 'mypolar' is % not affected. % ------------ % % % ---------------------------------------------------------------------------- % % LEGENDS: % % in.extra_legend in: %f % ex: Set to false if you don't want an extra_legend. % if in.extra_legend is a structure, it's equivalent % to in.extra_legend = true % def: true % ------------ % in.extra_legend structure containing: % name in: %s % ex: An associated string name. E.g., 'NaN','masked', etc... % def: mandatory % ------------ % type in: %s % ex: 'bg','fg', or 'nan', means use the color for the background, % foreground, or nan, given in in.color_background, in.color_foreground, % in.color_nan. So far these are the only 3 options. % def: mandatory % ------------ % fontsize in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: '10c' % ------------ % see in.legend of more options % ------------ % in.pscale in: %s % ex: 'gmt psscale OPT > filename.ps', explicitly sets the % psscale GMT command for the legend directly. % ------------ % in.pscale_extra in: %s % ex: 'gmt psscale OPT > filename.ps', explicitly sets the % psscale GMT command for the extra legend directly. % ------------ % in.savelegend in: %f % ex: Controls separate PDF for the legend. If value % is 0, only 1 file will be generated containing % map+legend. If value is 1, one file will be % generated for map+legend and one file for % legend only. If value is 2, one file will be % generated containing ONLY the map, and one % containing ONLY the legend. The legend will be % stored in [in.filename '_legend.' extension] in % the output directory. % def: false % ------------ % in.legend in: %f % ex: Set to false if you don't want a legend. if in.legend % is a structure, it's equivalent to in.legend = true % def: true % ------------ % % in.legend. | STRUCTURE with one or more of the following fields: % % fontsize % in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: Size of annotations in legend, e.g., '1c'=1cm % def: '14p' % ------------ % box_spacing in: %f. It has to be a number, and it seems to be in inches % ex: 0 % ------------ % equalboxwidth in: %f % ex: If the legend color boxes have to be the same size, 1 % def: false (-L option) % ------------ % extra_legend in: %s % ex: more instrucrtions to psscale. e.g. '-Li' % def: none % ------------ % justify in: %s % ex: sets the alignment. The alignment refers to the part of the legend % that will be mapped onto the (x,y) point. Choose a 2 character % combination of L, C, R (for left, center, or right) and % T, M, B for top, middle, or bottom. e.g., BL for lower left. % def: 'BR' % ------------ % length in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: Toggle the length of the legend bar (e.g., '3.9i'=3.9 inch). % Use negative value to reverse the legend. % See also width,xpos,ypos,orientation for legend % def: '3.9i' % ------------ % lineThick in: %f % ex: Thickness of the boxline surrounding legend % def: 0.2 % ------------ % map_annot_offset_primary % in: %s % ex: Move tick annotations to the right by x units, e.g. '.5i' (.5 inches) % ------------ % map_tick_length_primary % in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: The length of the ticks, e.g., '.5c'=.5cm % ------------ % map_tick_length_secondary % in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: The length of the ticks, e.g., '.5c'=.5cm % ------------ % orientation in: %s % ex: If you want a horizontal/vertical legend input 'h'/'v'. % def: Determined by map dimensions % ------------ % sidebar in: %f % ex: Input scalar 0, 1, 2, or 3. Indicates none, % below range only, above range, or both % def: Determined from data % ------------ % tick_annotations % in: {%s,%s,...} % ex: {'','','middle','',''}. Number of annotations must be = nlevels, and all % cell elements must be strings (or empty strings) % ------------ % tick_annotation_format % in: %s % ex: '%3.1e' % ------------ % tick_centering in: %f % ex: Have tick annotation at the center of the boxes % def: false (edges) % ------------ % tick_spacing in: %f % ex: If you want to manually decide how the ticks in the % legend should be spread. x=> every xth data value, % 1=>same number of ticks as datarange % def: One tick per data level % % NOTE: This option is desirable if you have many % data levels. % ------------ % xoffset in: %s % ex: offset the color scale by dx away from the refpoint % point in the direction implied by justify % def: '-1c' % ------------ % yoffset in: %s % ex: offset the color scale by dy away from the refpoint % point in the direction implied by justify % def: '-1c' % ------------ % width in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: Toggle the width of the legend bar. % See also length,xpos,ypos, orientation for legend % def: '.2i' %------------------------------------------------------------------------------------ % % NEARNEIGHBOR % % i) A string to be used for the system call to invoke nearneighbor % or % ii) A structure with one or more of the following fields: % % in.nearneighbor. | % % search in: %s % ex: '30m' Search for data within 30 [arcmin] % def: Loosely based on the density of the data points % ------------ % resolution in: %f [degree] % ex: 1 [deg] (== 60minutes) looks for values withing 1 [deg] % def: Loosely based on the density of the data points, or avialable memory % % % memGB in: %f % ex: 1 = allow up to 1Gib of memory to be used to make % the map. This could be useful of you have high % density and the default of 100Mib is not enough % to represent the data in the resolution you want % def: 2 % ------------------------------------------------------------------------ % % PROJECTION % % in.proj struct % projection in: %s % ex: See available projections in GMT manual. % def: 'Q' (cylindric equidistant) % ------------ % center in: %f % ex: Center map at given longitude % def: 0 [deg] % ------------ % map_width in: %s or %f (if %f, unit is assumed from in.proj_length_unit) % ex: Set width of map, e.g., '20c'=20cm % def: 20 (cm) % % or the string: % % in.proj in: %s % ex: 'projection/center/mapwidth' % def: 'Q0/20' (see above) %----------------------------------------------------------------- % PSBASEMAP % % in.psbasemap % % axes in: %s % ex: 'WSne', annotates West and South of the map, % but not North or East % def: 'WSne' % ------------ % ticks in: %s % ex: '60g30/30g15'. The first value for both axis % denotes the tick interval to annotate, the second % value is the interval to draw the gridlines. xaxis and % yaxis are separated by '/'. i.e (x-axis: % 'annot'g'grid'/ y-axis: 'annot'g'grid') % Note: To remove gridlines set the second value to 0. % def: Determined by input lat/lon range. %-------------------------------------------------------------------------- % PSCOAST % % --string ------ % in.pscoast in: %s (STRING COMMAND) % ex: 'pscoast OPT > filename.ps', explicitly sets the % pscoast GMT command. % --logical ----- % in.pscoast in: %f (LOGICAL TURN OFF) % ex: If you don't want coastlines, set to false. If % in.coast is a structure, it'll use the structure % arguments to generate the pscoast command. % def: To generate coastlines % --structure --- % % in.pscoast | STRUCTURE with one or more of the following fields: % % color in: %s % ex: Color of coast and rivers in rgb e.g. '255/255/255' % gives white coastlines % def: COLOR_BACKGROUND % ------------ % features in: %f % ex: Don't plot features < in.features [km^2] % def: determined by lon/lat range % ------------ % resolution in: %s % ex: (f)ull, (h)igh, (i)ntermediate, (l)ow, and (c)rude % def: determined by lon/lat range % ------------ % rivers in: %s % ex: Display rivers. pscoast -Ioption (1-10, a,r,i,c) % def: '1' (=major rivers) % ------------ % width in: %s % ex: Width of coastlines % def: '.3p' % ---------------------------------------------------------------- % LOCATION MARKERS: % % for multiple locations define all fields according to : % in.locations(1).text = 'x', % in.locations(2).text = 'y', etc... % % ---------------------- % % in.locations. | Structure with one or more of the following fields: % % lat in: %f % ex: Latitude of marker % def: mandatory user input % ------------ % lon in: %f % ex: Longitude of marker % def: manditory user input % ------------ % symbol in: %s % ex: Shape of marker. '' if you don't want one % def: 'c' c=filled circle. % more shapes: -,+,a,b,c,d,e,g,h,i,j,n, etc (see psxy -S) % ------------ % symbolsize in: %s % ex: Size of shape. % def: '5p' % ------------ % symbolcolor in: %s % ex: color of the symbol. % def: '0/0/0' % ------------ % symbolfill in: %s % ex: Color of marker % def: 'white' (or '255/255/255') % % see pstext for the text arguments (text,fontsize,angle,font,justify,color,outline) % for in.locations.(textarg) % %-------------------------------------------------------------------------- % % CONTOURS: % % in.contourline. | Structure with one or more of the following fields: % % color in: string, or {string, string, ...} matching % the number of levels % ------------ % fontsize in: %f [p] % ------------ % levels in: [%f %f ...] % ------------ % linethick in: %f % ------------ % more in: %s % ex: Additional commands for GMT's grdcontour. % E.g. '-T1c/0.001c:LH' % ------------ % spacing in: %f % ex: Data interval between contours. Only % relevant if you have not provided 'levels; % ------------ % range in: [%f %f] % def: in.datarange. Only % relevant if you have not provided 'levels; % % If several contour plots should overlap define all fields according to: % in.grdcontour(1).spacing = x, % in.grdcontour(2).spacing = y, etc... %-------------------------------------------------------------------------- % % PSPOLY: DRAWING LINES and SHAPES ON MAP % % in.pspoly.coord % in: {[%f %f;%f %f;...] [%f %f;...]} % ex: Draws a polygon based on coordinates. % Use one cell row per polygon region: % {[lon1 lat1; lon2 lat2; ...];...} % ------------ % in.pspoly.color in: contains strings: {'%f/%f/%f',...} % ex: RGB color or every region % def: {'0/0/0'} for every region (values range from 0 to 255) % ------------ % in.pspoly.thick in: {%s,...} % ex: Thickness of lines % def: 10p for every region boundary %-------------------------------------------------------------------------- % PSTEXT: Write text on a map % % in.pstext in: %s % ex: 'pstext OPT > filename.ps', explicitly sets % the pstext GMT command directly. If in.pstext is a % structure, it'll use the structure arguments to % generate the command. % ---------------------- % % in.pstext. | STRUCTURE with one or more of the following fields: % % for multiple text entries define all fields according to : % in.pstext(1).text = 'example', % in.pstext(2).text = 'example2', etc... % % angle in: %f % ex: degrees counter-clockwise from horizontal % def: 0 [deg] % ------------ % color in: %s % ex: RGB text color % def: '0/0/0' % ------------ % fontsize in: %s % ex: Text size in points % def: 10p % ------------ % font in: %s or %f % ex: Sets the font type % def: 1 % ------------ % justify in: %f % ex: The alignment refers to the part of the text % string that will be mapped onto the (x,y) point. % def: 'CM' % ------------ % lat in: %f % ex: Centre word at this latitude % def: manditory user input % ------------ % lon in: %f % ex: Centre word at this longitude % def: manditory user input % ------------ % outline in: %s % ex: '-S0.5p,0/0/0'. This gives a medium thick % black line around your text. The string follows % exactly the GMT pen definitions: GMT pen attributes % ---------- % text in: %s % ex: String to appear at lat/lon % def: manditory user input % ------------ % %-------------------------------------------------------------------------- % % % QUICK TEST % % in.lat = -89.5:89.5; % in.lon = -179.5:179.5 % in.data = rand(length(in.lat),length(in.lon)) % file = gmt_plot(in); % % ACKNOWLEDGEMENTS % % Copyright ?1991?2014 by P. Wessel, W. H. F. Smith, R. Scharroo, J. Luis and F. Wobbe % % The Generic Mapping Tools (GMT) is free software; you can redistribute it % and/or modify it under the terms of the GNU Lesser General Public License as % published by the Free Software Foundation. % % See also: GMT Docs % % Created by Salomon Eliasson and Oliver Lemke % $Id$ if nargin==2 assert(ischar(field),['atmlab:' mfilename ':badInput'],... ['The second argument to this function is supposed ',... 'to be the string name of data variable you wish to plot\n',... '(otherwise it is assumed that a field called ''data'' exists, ',... 'and this is the field it will try to plot)']) end if ~exist('field','var') && isfield(in,'data') field = 'data'; elseif isfield(in,'nodata') && in.nodata field = ''; end assert(logical(exist('field','var')) || (isfield(in,'nodata')&&in.nodata) ,... ['atmlab:' mfilename ':badInput'],... 'I need a "field".\nIf you want to plot the map with no data, input in.nodata=true') check_input(in,field) if isfield(in,'nodata')&&in.nodata out = set_GMT_plot(in); else out = set_GMT_plot(in,field); end % make TEMPDIR and set atmlab('VERBOSITY') to gmtlab('VERBOSITY') % cleanup after I'm done out.tmpdir = create_tmpfolder(); p = pwd(); V = atmlab('VERBOSITY'); cleanupObject = onCleanup(@() gmtcleanup(out.tmpdir,p,out.keep_files,V)); atmlab('VERBOSITY',gmtlab('VERBOSITY')); cd(out.tmpdir); if out.gridded out.grdfile = [out.filename '.nc']; gmt_nc_save_gridded(out.lon,out.lat,out.(field),out.grdfile); logtext(atmlab('VERBOSITY'),'Writing grdfile: %s sucessfull\n',out.grdfile) else if ~out.nodata out.ungriddedfile = [out.filename '.nc']; gmt_nc_save_ungridded(out.ungriddedfile,double(out.(field)),out.lat,out.lon); end end % main function commands = create_gmt_earth(out); % finalize filename=setup_figure(out,commands); end %%%%%%%%%%%%%%%%%%%% % SUBFUNCTIONS % |||| % vvvv function in = set_GMT_plot(in,field) %% SETUP %% % Test is GMT is installed and in the path [a,b] = system('which gmt'); if a == 1 error(['gmtlab:' mfilename ':shell'],[ [ b '\n\n'],... 'Either GMT is not installed\n',... 'or\n',... 'You need to add its installation directory to your PATH\n',... 'e.g. in Matlab you can add the PATH via \n',... 'setenv(''PATH'',[getenv(''PATH'') '':/bin''])']) else %This is gmt5 [~,in.gmtVersion] = system('gmt --version'); in.gmtVersion = strtrim(in.gmtVersion); end %logtext(atmlab('VERBOSITY'),'Using GMT version %s\n',in.gmtVersion) clear tmp if isfield(in,'color_background');in.forcedColorsBack=true;end if isfield(in,'color_foreground');in.forcedColorsFore=true;end if isfield(in,'color_nan') ;in.forcedColorsNan=true ;end % SET DEFAULTS % general default.add_offset = 0; default.color_background = '0/0/0'; default.color_foreground = '255/255/255'; default.color_nan = '150/150/150'; default.display = true; default.fill_value = []; default.font_title = 'Helvetica'; default.font_annot_primary = 'Helvetica'; default.fontsize_annot_primary = '14p'; default.font_annot_secondary = 'Helvetica'; default.fontsize_annot_secondary = '10p'; default.fontsize_title = '36p'; default.psbasemap.axes = 'nSeW'; default.scale_factor = 1; if exist('field','var') default.title = field; else default.title = 'noField'; end in = optargs_struct(in,default); clear default default.keep_files = false; % how far the title is removed from the top of the map (depends on 'N' or 'n') [fl,u2] = separate_integer_and_unit(in.fontsize_annot_primary); default.map_title_offset = sprintf('%.0f%s',0.1*fl + ... %half width of annotation ismember('N',in.psbasemap.axes)*fl,u2); default.force_nlevels = false; default.makeScriptAndStop = false; default.nodata = false; default.outdir = gmtlab('OUTDIR'); default.plotPlacement = '-Xa5 -Ya5'; default.proj.projection = 'N'; default.proj.map_width = '20c'; default.proj.center = 0; default.proj_length_unit = 'cm'; default.savelegend = 0; default.treatAsUngridded = false; default.unit = ''; default.xunit = ''; in = optargs_struct(in,default); in = orderfields(in); clear default in.outdir = sanitise(in.outdir); if strcmp(in.outdir(1),'~') % netcdffunctions don't like ~ for home in.outdir = [getenv('HOME') '/' in.outdir(2:end)]; end if system(sprintf('test -d %s -a -w %s',in.outdir,in.outdir)) error(['gmtlab:' mfilename ':BadInput'],... 'Either %s doesn''t exist, or atmlab does not have write access to %s',in.outdir,in.outdir) end if ~isempty(in.title) default.filename = ['gmtfile_' in.title]; default.filename = strrep(default.filename,'/','-'); else default.filename = 'default'; end in = optargs_struct(in,default); in = orderfields(in); clear default % find default figuretype b = regexp(in.filename,'\.'); if ~isempty(b) ft = in.filename(b(end)+1:end); if ismember(lower(ft),lower({'eps','pdf','BMP','JPEG','jpg','png','PPM','tif'})) default.figuretype = ft; else logtext(atmlab('VERBOSITY'),'figuretype: %s is not an available type. Will produce .pdf\n',ft); default.figuretype = 'pdf'; end if ~isfield(in,'figuretype') in.filename=in.filename(1:b(end)-1); end else default.figuretype = 'pdf'; end in = optargs_struct(in,default); in = orderfields(in); clear default in.filename = sanitise(in.filename); in.title = regexprep(in.title,'([()])','\\$1'); in.title = regexprep(in.title,':',' '); if in.nodata % If I only want an empty map, get the essentials and leave here. in = nodata(in); if ~ischar(in.proj) in = setup_projection(in); end return end if ~ismember(field,fieldnames(in)) error(['gmtlab:' mfilename ':BadInput'],... 'no field by the name of "%s" found in input structure',field) else in.fieldname = field; end if ~strcmp(field,'mask') && isfield(in,'mask') in.(field)(in.mask(in.(field))) = NaN; end % apply a function to the data first if ~strcmp(field,'func') && isfield(in,'func') logtext(atmlab('VERBOSITY'),'Applying "%s" to the datafield "%s"\n',func2str(in.func),field) in.(field) = in.func(in.(field)); end % use fillvalue, add_offset, and scale_factor if ~isempty(in.fill_value) in.(field) = double(in.(field)); in.(field)(in.(field) == in.fill_value) = NaN; end if ~isempty(in.scale_factor) in.(field) = double(in.(field)).*in.scale_factor + in.add_offset; end % is the data plotable? in = isdata(in,field); % Check to see if the data is gridded or not in = isgridded(in,field); % special patch if you want to test if you want to switch to treating gridded % data ungridded in = treatAsUngridded(in,field); % Also get rid of NaNs in the geodata (This only applies to ungridded data) in = rmNaNsGeo_and_standardize_geodata(in,field); % REGION in = assign_region(in,field); in = specialregion(in,field); %in case the region covers the dateline and is not continuous % get default ticks for map borders in = getDefaultTicks(in); % DATARANGE if ~in.nodata default.datarange = getdatarange(in,field); % if any(isnan(in.(field)(:))) && isfield(in,'legend')&&isstruct(in.legend) default.extra_legend = struct('name','NaN','type','nan'); % include NaNs in the legend else if isfield(in,'extra_legend') && ... isstruct(in.extra_legend) && ... strcmpi(in.extra_legend.name,'NaN') && ... in.gridded logtext(atmlab('ERR'),'Asked to make a NaN-legend although none are present in the data\n') end default.extra_legend = false; end end in = optargs_struct(in,default); in = orderfields(in); clear default in.datarange = double(in.datarange); %assert(length(unique(in.(field)((in.(field))>=in.datarange(1)&in.(field)<=in.datarange(2))))>2,... % 'It appears all data is outside the range of in.datarange, or you only have 2 valid values') % if you have only given the min value if isscalar(in.datarange), tmp=getdatarange(in,field);in.datarange(2) = tmp(2);end assert(diff(in.datarange)>0,'atmlab:badInput','min=max in the datarange. Aborting...') % COLORS & DATA REPRESENTATION if ~isfield(in,'colorrange') % because this has precendence default.ctable = 'rainbow'; % color palette end default.nlevels = get_nlevels(in); in = optargs_struct(in,default); clear default [default.stepsize,default.tick_annotation_format] = getstepsize(in); % color level datastep interval in = optargs_struct(in,default); clear default % LEGEND if ~ (isfield(in,'legend') && (~isstruct(in.legend) && ~in.legend)) % If in.legend = false, do nothing in.legend = setupLegend(in); % extra LEGEND if isstruct(in.extra_legend) in = setupXtraLegend(in); end end if ~ischar(in.proj) in = setup_projection(in); end in = orderfields(in); end function nl = get_nlevels(in) dr = in.datarange; % set some reasonable limits for how many levels I want and the min and max % levels des_nlev = 8; % desired levels minlev = 5; % minium levels maxlev = 100; % maximum levels % Leave if there aren't enough values to make the minium number of levels and set it to that uq = unique(in.(in.fieldname)(... in.(in.fieldname)>=dr(1) & ... in.(in.fieldname)<=dr(2) ... ) ); if length(uq)<=des_nlev nl = length(uq); return end % loop over reasonable levels to see which datarange split into nlevels will % look best. a = des_nlev:-1:minlev; a = a(2:end); b = des_nlev:maxlev; % make a vector of desired levels with the desired number first nlvec(1:2:2*length(a)-1) = b(1:length(a)); nlvec(2:2:2*length(a)) = a; nlvec(2*length(a)+1:length(b)+length(a)) = b(length(a)+1:end); pick=nan(1,length(nlvec));indic=pick; i=1; for nl = nlvec for fc = [1,10,100,1000,1000,10000] if abs(fc*(dr(2)-dr(1)) /nl) - floor(fc*(dr(2)-dr(1)) /nl) < 1e-6 pick(i)=fc; indic(i)=i; break end end i=i+1; end indic=indic(~isnan(pick)); nlvec=nlvec(~isnan(pick)); pick=pick(~isnan(pick)); % choose the lowest value "distance to desired nlevel" * "accuracy" (pick) nl = nlvec(indic.*pick==min(indic.*pick)); if isempty(nl) nl=des_nlev; end end %% SUBFUNCTIONS FOR SETUP % |||||| % VVVVVV function check_input(in,field) %% CheckInput % Checks the input type of all options. If the field is in the list of % available options, it will be tested against a anomynous function (e.g. % in.region should pass @(x)(ischar(x)) ). The field names and their % corresponding test functions are listed in gmt_inputs. errID = ['gmtlab:' mfilename ':BadInput']; assert(isstruct(in),errID,'"in" must be a structure') [GSE,GSSE] = gmt_inputs; Fields = fieldnames(in)'; fieldsNotToRead = cell(100,1); ii = 1; for F = Fields f = F{1}; if strcmp(f,field) && ismember(field,GSE(:,2)) logtext(atmlab('ERR'),['in.%s is ordinarily a gmt option, but "field"=%s ',... 'has precedence and thus in.%s will be treated as data\n'],f,f,f) continue end % See if the in.(field) is a listed option test = GSE(ismember(GSE(:,2),f),:); if ~isempty(test) % corresponding test function fun = test{1}; strfun = func2str(fun); assert(fun(in.(test{2})),errID,... 'Input type is incorrect. in.%s should pass the test: %s',test{2},strfun); % If in.(field) is itself a structure, do it again (more or less) if isstruct(in.(test{2})) && ismember(test{2},fieldnames(GSSE)) Fields2 = fieldnames(in.(test{2}))'; for F2 = Fields2 f2 = F2{1}; test2 = GSSE.(test{2})(ismember(GSSE.(test{2})(:,2),f2),:); if ~isempty(test2) fun = test2{1}; strfun = char(fun); if ~fun(in.(test{2})(1).(test2{2})) logtext(atmlab('ERR'),'Input type is incorrect. in.%s.%s should pass the test: @%s\n\n',... test{2},test2{2},strfun) logtext(atmlab('ERR'),'in.%s.%s = ',test{2},test2{2}) disp(in.(test{2})(1).(test2{2})) error(errID,... 'Input type is incorrect. in.%s.%s should pass the test: @%s',... test{2},test2{2},strfun); end else warning(errID,'in.%s.%s is not a valid field in in.%s ',... test{2},f2,test{2}) end end end else if ~any(strcmp(f,{field,'lat','lon'})) fieldsNotToRead{ii} = f; ii= ii+1; end end end if ~all(cellfun(@isempty,fieldsNotToRead)) logtext(atmlab('ERR'),... 'Warning: Fields: "%s",Will not be used as they are not "data" or in the list of options\n',... fieldsNotToRead{1:ii-1}); end end function in = assign_region(in,field) errId = ['gmtlab:' mfilename ':badInput']; % common error ID if ~isfield(in,'region') if in.gridded % check if the data is probably global dn = diff(in.lon); dt = diff(in.lat); if isempty(dt), dt = mean(dn);end %incase there's only one latitude % make the region str, keeping in min the 'resolution' of the grid annot_format = {getAnnotFormat(mean(diff(in.lon))),... getAnnotFormat(mean(diff(in.lat)))}; % check to see if lon-dlon is out side the boundary. If so, fix the % region to the edges. Same goes for lat... % If the region passes the dateline it goes back to 0:360 notation if in.lon360 cond1 = abs(0 - in.lon(1)) <= dn(1); cond2 = 360-in.lon(end) <= dn(end); else cond1 = abs(-180 - in.lon(1)) <= dn(1); cond2 = 180-in.lon(end) <= dn(end); end cond3 = abs(-90-in.lat(1)) <= dt(1); cond4 = 90-in.lat(end) <= dt(end); af = annot_format{1}; if cond1 && cond2 && cond3 && cond4 if in.lon360 in.region= '0/360/-90/90'; else in.region= '-180/180/-90/90'; end elseif cond1 && cond2 if in.lon360 fstr = sprintf('0/360/%s/%s',af,af); else fstr = sprintf('-180/180/%s/%s',af,af); end in.region = sprintf(fstr,in.lat(1)-dt(1)/2,in.lat(end)+dt(end)/2); elseif cond3 && cond4 fstr = sprintf('%s/%s/-90/90',af,af); in.region = sprintf(fstr,in.lon(1)-dn(1)/2,in.lon(end)+dn(end)/2); else fstr = sprintf('%s/%s/%s/%s',af,af,af,af); in.region = sprintf(fstr,... in.lon(1)-dn(1)/2,in.lon(end)+dn(end)/2,... in.lat(1)-dt(1)/2,in.lat(end)+dt(end)/2); end else if (max(in.lon(:))-min(in.lon(:)) ) >= 350 if ~in.lon360 in.region = '-180/180'; else in.region = '0/360'; end else in.region = sprintf('%g/%g',... min(in.lon(:)),max(in.lon(:))); end if (max(in.lat(:))-min(in.lat(:)) ) >= 170 in.region = sprintf('%s/-90/90',in.region); else in.region = sprintf('%s/%g/%g',... in.region,min(in.lat(:)),max(in.lat(:))); end end else in.userDefinedRegion = true; x = sscanf(in.region,'%f/%f/%f/%f'); if in.lon360 && (x(1) < 0) if ~in.gridded in.lon = in.lon-(in.lon > 180)*360; else [~,tmp] = standardize_geodata(struct('lat',in.lat,'lon',in.lon,'data',in.(field)),struct('lon360',0)); in.lon = tmp.lon; in.(field) = tmp.data; end end end if strcmp(in.region,'g') % Then the data is set to global if ~in.lon360 in.region = '-180/180/-90/90'; else in.region = '0/360/-90/90'; end return end % remove frivolous data for memory and speed performance in that case x = sscanf(in.region,'%f/%f/%f/%f'); lt = in.lat >= x(3) & in.lat <=x(4); %logical vector ln = in.lon >= x(1) & in.lon <=x(2); %logical vector if in.gridded in.(field) = in.(field)(lt,ln); in.lat = in.lat(lt); in.lon = in.lon(ln); else in.(field) = in.(field)(lt&ln); in.lat = in.lat(lt&ln); in.lon = in.lon(lt&ln); end assert(~isempty(in.lat) && ~isempty(in.lon),... errId,'lat or lon are empty') end function datarange = getdatarange(in,field) %% getdatarange tmpdata = in.(field)(~isnan(in.(field))); tmp = unique(tmpdata); if isfield(in,'nlevels') lim=in.nlevels; else lim=10; end if length(tmp)>=lim %sneakily remove the outliers from contention tmp=tmp(min([length(tmp)-1,2]):end); end d = double([min(tmp(~isinf(tmp))) max(tmp(~isinf(tmp)))]); % Do some tricks to get the most useful datarange. (rounded, but preserve precision) x = log10(d(2)-d(1)); datarange = [10^(round(x)-1) * round(d(1) * (1/10^(round(x)-1))),... 10^(round(x)-1) * round(d(2) * (1/10^(round(x)-1)))]; end function [stepsize,annot_format] = getstepsize(in) %% getstepsize if in.nodata stepsize = 0; annot_format=''; return end if isfield(in,'stepsize') % warning('gmtlab:input','in.stepsize overrides in.nlevels') stepsize = in.stepsize; elseif ~isfield(in,'stepsize') stepsize = (in.datarange(2)-in.datarange(1))/in.nlevels; end annot_format = getAnnotFormat(stepsize); end function in = nodata(in) %% NODATA % if you want to only plot coastlines in.sidebar = false; in.legend = false; in.gridded = false; in.nlevels = 1; % a stab at the region before something more elaborate is done in = preGetRegion(in); % get default ticks for map borders in = getDefaultTicks(in); end function in = preGetRegion(in) if isfield(in,'region') % do nothing elseif ~isfield(in,'region') && all(isfield(in,{'lat','lon'})) in.region = sprintf('%g/%g/%g/%g/%g',... min(in.lon(:)),max(in.lon(:)),... min(in.lat(:)),max(in.lat(:))); else in.region='-180/180/-90/90'; end end function in = setup_projection(in) %% setup_projection %% P = in.proj; x = sscanf(in.region,'%f/%f/%f/%f'); allLongs = abs((abs(x(1))+abs(x(2)) ) - 360) <1; % allow within one degree if ~isfield(P,'center') || ~allLongs % if not global if ~allLongs && isfield(P,'center') logtext(atmlab('ERR'),'There are strange artifacts if the data doesn''t cover all longitudes and P.center is not close to the middle (like now).\n') logtext(atmlab('ERR'),'Resetting to P.center to the center of the longitude range. P.center = %g.\n',(x(1)+x(2))/2) logtext(atmlab('ERR'),'If you desparately need the map centered somewhere else try in.makeScriptAndStop=true, tweak the -Q entry in psbasemap in the generated script manually, and run the script on the command line\n') end P.center = (x(1)+x(2))/2; end in.proj = sprintf('%s%g/%s',P.projection,P.center,num2str(P.map_width)); end function in = getDefaultTicks(in) %% getDefaultTicks % % Set the ticks around the borders of the map % % % requires in.region and in.psbasemap % 'g' seems to not work so well % if strcmp(in.region,'g') % if in.lon360 % rg = [0 360 -90 90]; % else % rg = [-180 180 -90 90]; % end % else rg=sscanf(in.region,'%f/%f/%f/%f'); %end ln=rg(2)-rg(1); lt=rg(4)-rg(3); % for nice step lengths % lons as = [240 180 120 90 60 30 15]; step = [60 40 30 20 15 10 5]; a = step(~(as>ln)); if isempty(a), a = ln/6; end %lats bs = [120 90 60 40 30 20 15]; step = [60 30 30 20 10 5 2.5]; b = step(~(bs>lt)); if isempty(b), b = lt/14; end default.ticks = sprintf('%gg%g/%gg%g',a(1),b(1),a(1)/2,b(1)/2); in.psbasemap = optargs_struct(in.psbasemap,default); end function in = isdata(in,field) %% is there any useable data? Id = ['gmtlab:' mfilename ':badInput']; Eps = 1e-10; % silently squeeze the data in.(field)=squeeze(in.(field)); assert(isfield(in,field), Id,'The field: "%s" is not in the structure',field) assert(any(~isnan(in.(field)(:))),'gmtlab:gmt_plot:noData',... '%s%s','Data does not contain any valid',... ' values for contour levels to be based on...') assert(~(~ismatrix(in.(field))),Id,'in.%s must not be more than 2 dimensional',field) assert((max(in.(field)(:))-min(in.(field)(:))>Eps),Id,... 'min(data) must not be equal to max(data)') % check for lat and lons if ~isfield(in,'lat') l = {'Latitude','latitude'}; assert(any(isfield(in,l)),Id,'No latitudes present') in.lat = in.(l{isfield(in,l)}); elseif any(isfield(in,{'Latitude','latitude'})) logtext(atmlab('ERR'),'Several latitude vectors present. Using in.lat') end if ~isfield(in,'lon') l = {'Longitude','longitude','long'}; assert(any(isfield(in,l)),Id,'No longitudes present') in.lon = in.(l{isfield(in,l)}); elseif any(isfield(in,{'Longitude','longitude','long'})) logtext(atmlab('ERR'),'Several longitude vectors present. Using in.lon') end end function in = isgridded(in,field) %% ISGRIDDED: Test if the data is gridded % If it is not gridded, flatten the data if isempty(in.(field)) error(['gmtlab:' mfilename ':BadInput'],.... 'The data variable: in.%s is empty',field) end if ndims(in.(field))==3 in.(field) = squeeze(in.(field)); end [a,b]=size(in.(field)); pos1 = a==length(in.lat)&b==length(in.lon); %(lat,lon) pos2 = b==length(in.lat)&a==length(in.lon); %(lon,lat) % if (pos1 && pos2) && (pos1 || pos2), then we dont know if it gridded, but % assume it's not. if ~isfield(in,'gridded') ig = ~ (isequal(size(in.(field)(:)),size(in.lat(:)),size(in.lon(:)))); if ~ig in.(field) = in.(field)(:); in.lat = in.lat(:); in.lon = in.lon(:); in.gridded = false; else if pos2 && (pos2 ~= pos1) % want it in (lat,lon) in.(field)=in.(field)'; end in.gridded = true; end end end function in = treatAsUngridded(in,field) if in.treatAsUngridded && in.gridded logtext(atmlab('VERBOSITY'),'Treating the gridded data as ungridded\n') in.lon = repmat(in.lon',length(in.lat),1); in.lat = repmat(in.lat,1,length(in.lon)); in.gridded = false; assert(isequal(size(in.lat),size(in.lon),size(in.(field))),'Something went wrong') end end function in = rmNaNsGeo_and_standardize_geodata(in,field) %% rmNaNsGeo % Also get rid of NaNs in the geodata % This only applies to ungridded data errId = ['gmtlab:' mfilename ':badInput']; % common error ID if ~in.gridded index = in.lat >= -90 & in.lat <= 90 & in.lon >= -180 & in.lon <= 360; if any(~index) logtext(atmlab('VERBOSITY'),'Data with dodgy geodata will be ignored (%.2f%%)\n',100*sum(~index)/numel(index)) assert(sum(index)/numel(index)>0,['atmlab:',mfilename,':badInput'],'No valid lat lons!\n') in.lat = in.lat(index); in.lon = in.lon(index); in.(field) = in.(field)(index); end in.lon360 = ~(min(in.lon(:))<0 || ~max(in.lon(:))>180); else % make sure that the lons,lats and data are ordered in ascending and data(lat,lon) %if in.lon360 && max(diff(in.lon))>5*min(diff(in.lon)) % if the are discontinuous assert(length(in.lat)*length(in.lon)==numel(in.(field)),... errId,'numel(data) must length(lat)*length(lon)') % standardize dataOptions.dimorder='latlon'; dataOptions.lat_descend=false; dataOptions.lon_descend=false; dataOptions.duplicate=false; [~,tmp] = standardize_geodata(struct('lat',in.lat,'lon',in.lon,'data',in.(field)),dataOptions); in.lat=tmp.lat; in.lon=tmp.lon; in.(field)=tmp.data; clear tmp % shift to 360 degrees if possible if ~isfield(in.proj,'center') dataOptions.lon360 = true; [~,tmp] = standardize_geodata(... struct('lat',in.lat,'lon',in.lon,'data',in.(field)),dataOptions); if max(diff(tmp.lon))>5*min(diff(tmp.lon)) logtext(1,'Did not convert to 0:360 grid') in.lon360=false; else in.lat=tmp.lat; in.lon=tmp.lon; in.(field)=tmp.data; clear tmp in.lon360=true; end else in.lon360=false; end end end function in = setupXtraLegend(in) default.width = in.legend.width; in.extra_legend = optargs_struct(in.extra_legend,default); clear default default.length = in.extra_legend.width; default.lineThick = in.legend.lineThick; default.justify = 'RB'; default.fontsize = in.fontsize_annot_primary; default.unit = false; [lw,lwu] = separate_integer_and_unit(in.legend.width); if strcmp(in.legend.orientation,'h') if isstruct(in.proj) p=in.proj.projection; else p=in.proj(1); end switch p case {'H','I'} % hammer,sinusoidal projOff = 5; case 'N' % robinsson projOff = 2.5; case 'R' %winkel-trippel projOff = 2; otherwise projOff = 1; end default.xoffset = sprintf('%.2f%s',2*lw*(1+projOff),lwu); default.yoffset = '0.2c'; % not a general solution elseif strcmp(in.legend.orientation,'v') default.xoffset = '0'; default.yoffset = sprintf('%.2f%s',lw*2,lwu); end in.extra_legend = optargs_struct(in.extra_legend,default); in.extra_legend.extra_instructions = '-Li'; % no ticks, square end function legend = setupLegend(in) % SETUP LEGEND errId = ['gmtlab:' mfilename ':badInput']; % common error ID if ~isfield(in,'legend') || ~isstruct(in.legend) in.legend=struct(); end default.lineThick =.2; %thickness of legend bar % apply defaults to legend %% ORIENTATION % This only applies to rectangular grids if isstruct(in.proj) p=in.proj.projection(1); else p=in.proj(1); end switch p case {'S','s'} default.orientation='v'; f=1; otherwise x = sscanf(in.region,'%f/%f/%f/%f'); f = ( x(4)-x(3))/ ( x(2)-x(1) ); %something between 0->1 equivalent to 180/360->120/360 lats/lons myHcondition = f <= 0.25; tmp = {'v','h'}; %vertical,horisontal default.orientation = tmp{myHcondition+1}; end legend = optargs_struct(in.legend,default); %need orientation now % I use map_width to find the defaults. if ischar(in.proj) x = regexp(in.proj,'.*/(?.+)','names'); mwstr = x.map_width; else mwstr = in.proj.map_width; end [mw,u] = separate_integer_and_unit(mwstr); [fl,u2] = separate_integer_and_unit(in.fontsize_annot_primary); if strcmp(u2,'p') && strcmp(u,'c') % convert to c fl=fl*0.035; u2='c'; end % thickness of the legend default.width = sprintf('%.2f%s',2*fl,u2); legend = optargs_struct(legend,default); %need orientation now switch legend.orientation case 'v' default.justify = 'MR'; %middle right default.length = sprintf('%.2f%s',f*mw,u); if ismember('E',in.psbasemap.axes) default.xoffset = sprintf('%.2f%s',2.5*fl,u2); elseif ismember('e',in.psbasemap.axes) default.xoffset = sprintf('%.2f%s',0.7*fl,u2); end default.yoffset = '0'; case 'h' default.justify = 'BC'; % bottom centre default.length = sprintf('%.2f%s',mw,u); default.xoffset = '0'; if ismember('S',in.psbasemap.axes) default.yoffset = sprintf('%.2f%s',2*fl,u2); elseif ismember('s',in.psbasemap.axes) default.yoffset = sprintf('%.2f%s',0.5*fl,u2); end otherwise error(errId,'orientation should be "v" or "h", not "%s"',in.legend.orientation) end default.tick_annotion_format = in.tick_annotation_format; if ~isfield(legend,'tick_annotations') % incompatible default.tick_spacing = (in.datarange(2)-in.datarange(1))/in.nlevels ... * (floor((in.nlevels-1)/10) + 1); %10 is the old default number of levels end default.sidebar = getsidebar(in); % coloroured triangles using in.datarange legend = optargs_struct(legend,default); % add unit and xunit legend.unit = in.unit; legend.xunit = in.xunit; % make the legend special if there are only two to 4 unique values lim=2; if length(unique(in.(in.fieldname)))<=lim+1 if ~isfield(in.legend,'box_spacing'); legend.box_spacing = 1;end if ~isfield(in.legend,'tick_centering'); legend.tick_centering = true;end if ~isfield(in.legend,'tick_annotations') v=unique(in.(in.fieldname)); for i=1:length(v) legend.tick_annotations{i} = num2str(v(i)); end end end end function sidebar = getsidebar(in) %% color level datastep interval % sidebar can have the values 0,1,2,3 (none, below only, above only, both). sidebar = ( min(in.(in.fieldname)(:))in.datarange(2) ); end function in = specialregion(in,field) if in.gridded % DO some special treatment of data crossing the dateline that is not continuous % after changing to -180:180 regime OR if in.region is in 0:360 mode if any(diff(in.lon)==0) % in case a longitude appears twice. e.g., if the grid was originally % lon = 0:360 cond = diff(in.lon)~=0; in.lon = in.lon(cond); in.(field) = in.(field)(:,cond); end if in.lon360 && max(diff(in.lon))>5*min(diff(in.lon)) % if the are discontinuous in.lon = in.lon+(in.lon < 0)*360; [in.lon,lnindex] = sort(in.lon); in.(field) = in.(field)(:,lnindex); % if diff equal 0 somewhere then at best the data is repeated e.g. % at 0 and 360 degrees if any(diff(in.lon)==0) if isequal(in.(field)(:,find(diff(in.lon)==0)),in.(field)(:,find(diff(in.lon)==0)+1)) %#ok % then remove duplicate line in.lon = in.lon(diff(in.lon)~=0); in.(field) = in.(field)(:,diff(in.lon)~=0); else error(['atmlab:' mfilename],'Same longitudes are repreated but the data are not the same there') end end % Do the region part again if ~isfield(in,'userDefinedRegion') || ~in.userDefinedRegion in = rmfield(in,'region'); in = assign_region(in,field); end end assert(max(diff(in.lon))<5*min(diff(in.lon)),['atmlab:' mfilename, ':Error'],... 'The longitudes are not continuous. FIXME: put workaround in place') if length(in.lat) == size(in.(field),1)+1 || length(in.lon) == size(in.(field),2)+1 logtext(atmlab('ERR'),'Data does not appear to be centered. Internally ') in = centerGeoData(in,field); end end end % ---------------- % Other subfunctions % function filename = setup_figure(in,command) %% SORT FIGURES %'eps','pdf','BMP','jpg','png','PPM','TIFF', ot 'tif' switch in.figuretype case 'pdf' T = '-Tf'; case 'bmp' T = '-Tb -Qg -Qt'; case 'eps' T = '-Te'; case 'jpg' T = '-Tj -Qg -Qt'; case 'png' T = '-Tg -Qg -Qt'; %-Q is for no antialiaing 'g'raphics and 't'ext case 'tif' T = '-Tt -Qg -Qt'; case 'ppm' % is not recommended T = '-Tm -Qg -Qt'; otherwise error(['gmtlab:' mfilename ':FigureType'],... '%s: not supported',in.figuretype) end command{end+1} = sprintf('gmt psconvert %s.ps -A -P %s',in.filename,T); % get rid of whitespace if strcmp(in.figuretype,'pdf') command{end+1} = sprintf('pdfcrop %s.pdf',in.filename); command{end+1} = sprintf('mv %s-crop.pdf %s.pdf',in.filename,in.filename); if isnan(gmtlab('PDFVIEWER')) openwith = gmtlab('OPEN_COMMAND'); else openwith = gmtlab('PDFVIEWER'); end else openwith = gmtlab('OPEN_COMMAND'); end if in.makeScriptAndStop filename = makeScriptAndStop(command); logtext(atmlab('ERR'),'Stopped\n') return end % move the plot command{end+1} = sprintf('mv %s.%s %s',in.filename,in.figuretype,in.outdir); % display it if in.display && ~any(isnan(openwith)) command{end+1} = sprintf('%s %s/%s.%s >/dev/null &',... openwith,in.outdir,in.filename,in.figuretype); end if in.savelegend % MAKE a separate file for the legend findPsScale = ~cellfun('isempty',regexp(command,'psscale')); if ~any(findPsScale) v = atmlab('VERBOSITY'); atmlab('VERBOSITY',1); logtext(atmlab('ERR'),'psscale, which is used to make the legend was never called. Maybe legend=0 or there was no input data?\n') atmlab('VERBOSITY',v); filename = ''; return end % make the history and config files for i = 1:find(~cellfun('isempty',regexp(command,'psbasemap'))==1, 1 ) exec_system_cmd(command{i}); end in.filename_legend = makelegend(in,command(findPsScale)); if in.savelegend==2 % store legend ONLY in external file command = command(~findPsScale); end command{end+1} = sprintf('mv -v %s.%s %s',in.filename_legend,in.figuretype,in.outdir); end out = exec_system_cmd(command,gmtlab('verbosity')); % execute all gathered commands filename = sprintf('%s/%s.%s',in.outdir,in.filename,in.figuretype); % TRY and open the file even without user input % If no viewer is defined, check for xpdf, then evince, then okular if in.display && any(isnan(openwith)) logtext(atmlab('ERR'),'display = 1, yet no viewer is defined\nE.g. gmtlab(''pdfviewer'',''gnome-open'')\n') if strcmp(in.figuretype,'pdf') logtext(atmlab('VERBOSITY'),'No pdfviewer defined.\n') logtext(atmlab('VERBOSITY'),'Set gmtlab(''PDFVIEWER'','''') in your startup file\n') if ~system('which xpdf') openwith = 'xpdf'; elseif ~system('which evince') openwith = 'evince'; % for gnome users elseif ~system('which okular') openwith = 'okular'; % for kde users end if ~any(isnan(openwith)) logtext(atmlab('VERBOSITY'),'Opening file with %s for now\n',openwith) system(sprintf('%s %s &',openwith,filename)); end end end hunt_down_errors(command,out) % look for system call errors in the gmt calls logtext(atmlab('VERBOSITY'), 'GMT plot stored at: %s\n',filename) if in.savelegend logtext(atmlab('VERBOSITY'), 'GMT plot legend stored at: %s.%s\n',in.filename_legend,in.figuretype) end end function file = makelegend(in, command) %% Make a separate file for the legend % Only allowed to have 1 or 2 psscale commands in the commad list. the first one % is the regular legend. The second is for the nan legend (if it exists) file = [in.filename,'_legend']; %Remove trailing .ps entry crop=regexp(command,'>>'); % Additional adjustments for i = 1:length(command) %for i = 2:length(command) command{i} = sprintf('%s -P >> %s.ps', command{i}(1:crop{i}-1), file ); if i == 1 command{i} = regexprep(command{i},'-O',''); end if i == length(command) command{i} = regexprep(command{i},'-K',''); end end switch in.figuretype case 'pdf' T = '-Tf'; case 'bmp' T = '-Tb -Qg -Qt'; case 'eps' T = '-Te'; case 'jpg' T = '-Tj -Qg -Qt'; case 'png' T = '-Tg -Qg -Qt'; %-Q is for no antialiaing 'g'raphics and 't'ext case 'tif' T = '-Tt -Qg -Qt'; case 'ppm' % is not recommended T = '-Tm -Qg -Qt'; otherwise error(['gmtlab:' mfilename ':FigureType'],... '%s: not supported',in.figuretype) end command{end+1} = sprintf('gmt psconvert %s.ps -A -P %s',file,T); exec_system_cmd(command,gmtlab('verbosity')); end function hunt_down_errors(command,errors) %% Hunt down any errors encounted using GMT % messages containing any of these are exempted error_exeptions = {'warning','not set'}; definite_errors = {'illegal','error'}; % this gives cell{1:nerrors}{1:nerr_exp} found_err_exp = cellfun(@(x)(regexp(x,error_exeptions,'ignorecase','once')),errors,'uniformoutput',0); found_def_err = cellfun(@(x)(regexp(x,definite_errors,'ignorecase','once')),errors,'uniformoutput',0); windex = cellfun(@(x)(any(cell2mat(x))),found_err_exp); eindex = cellfun(@(x)(any(cell2mat(x))),found_def_err); Warnings = errors(windex); associated_w_warning = command(windex); Errors = errors(eindex); associated_w_errors = command(eindex); % display GMT calls containing warning if ~isempty(Warnings) %V = atmlab('VERBOSITY'); atmlab('VERBOSITY',1); logtext(atmlab('VERBOSITY'),'GMT completed with the following warning messages:\n') tmp = cellfun(@(x,y)(sprintf('%s\n%s\n',x,y(1:end-1))),associated_w_warning,Warnings,'UniformOutput',0); logtext(atmlab('VERBOSITY'),'%s\n',[tmp{:}]) %atmlab('VERBOSITY',V); end % error on GMT errors if ~isempty(Errors) tmp = cellfun(@(x,y)(sprintf('%s\n%s\n',x,y(1:end-1))),associated_w_errors,Errors,'UniformOutput',0); error(['gmtlab:' mfilename ':GMT'],... 'GMT encountered the following errors:\n%s',[tmp{:}]) end end function gmtcleanup(tmpdir,curdir,keepfiles,V) %% cleanUp if keepfiles logtext(atmlab('VERBOSITY'),'in.keep_files = true;\nTemporary files are stored at %s.\n',pwd) logtext(atmlab('ERR'),'Remember to delete the directory when you are done\n') else % cd back to the original directory cd(curdir); rmdir(tmpdir,'s') logtext(atmlab('VERBOSITY'),'%s is now removed\n',tmpdir) atmlab('VERBOSITY',V); end end function filename = makeScriptAndStop(command) %% Make a script for debugging logtext(atmlab('VERBOSITY'),'Making directory to put scripts and plot data into\n') folder = sprintf('%s/gmt_test_%s',gmtlab('OUTDIR'),datestr(now,'yyyymmdd_HHMMSS')); exec_system_cmd(sprintf('mkdir %s',folder)); exec_system_cmd(sprintf('rsync -avP %s/* %s/.',pwd,folder)); curdir=pwd; cd(folder) logtext(atmlab('VERBOSITY'),'Making file: %s/script.sh\n',folder) fid = fopen('script.sh','w'); fprintf(fid,'#!/bin/bash\n'); fprintf(fid,'\n\n\n'); fprintf(fid,'cd %s\n\n',folder); for C = command fprintf(fid,'%s\n',C{1}); end exec_system_cmd(sprintf('chmod +x %s/script.sh',folder)); filename = fullfile(folder,'script.sh'); a = exec_system_cmd(sprintf('du -h %s',gmtlab('OUTDIR'))); logtext(atmlab('ERR'),'\nDon''t forget to clean up\n%swhen you are done!\n',a{1}) cd(curdir) end