├── README.md ├── arrows ├── README.md ├── arrows.m └── arrows_example.png ├── comprose ├── README.md ├── comprose.m └── comprose_example.png ├── dem ├── README.md ├── dem.m ├── dem_example_dome.png ├── dem_example_guadeloupe.png ├── dem_example_indonesia.png ├── dem_example_moon_bone.png ├── landcolor.m └── seacolor.m ├── grd ├── egrd.m └── igrd.m ├── greatcircle ├── README.md ├── greatcircle.m ├── greatcircle_example.png └── loxodrome.m ├── ibil └── ibil.m ├── latlonutm ├── README.md ├── ll2utm.m └── utm2ll.m ├── polarmap ├── README.md ├── polarmap.m └── polarmap_example.png ├── radiocover ├── README.md ├── radiocover.m └── radiocover_example.png └── readhgt ├── README.md ├── readhgt.m ├── readhgt_example_misti.png ├── readhgt_example_paris.png ├── readhgt_example_seattle.png ├── readhgt_example_tanzania.png └── readhgt_srtm_index.txt /README.md: -------------------------------------------------------------------------------- 1 | # mapping-lib 2 | Various mapping functions mostly written in Matlab/Octave (but Python equivalent will come): DEM manipulation, SRTM automatic download, topographic maps, coordinates convert and paths, ... 3 | 4 | ## Topographic grids 5 | - ### [READHGT](readhgt) 6 | Download/read NASA SRTM worldwide topographic data files (.HGT). 7 | 8 | - ### [DEM](dem) 9 | Shaded relief image plot for digital elevation models. 10 | 11 | - ### [WHEREAMI](https://github.com/beaudu/whereami) 12 | Automatic geolocation map (other repository). 13 | 14 | - ### [IBIL](ibil) 15 | Import ESRI BIL raster binary files. 16 | 17 | - ### [IGRD and EGRD](grd) 18 | Import and export ESRI/Arcinfo and GS/Surfer ASCII or binary GRD DEM files. 19 | 20 | - ### [NANINTERP2](https://github.com/beaudu/naninterp2) 21 | 2-D optimized linear interpolation to fill gaps in a grid (other repository). 22 | 23 | ## Coordinates and paths 24 | - ### [LL2UTM and UTM2LL](latlonutm) 25 | Latitude/longitude to and from UTM coordinates precise and vectorized conversion. 26 | 27 | - ### [GREATCIRCLE and LOXODROME](greatcircle) 28 | Shortest and rhumb line path, distance and bearing. 29 | 30 | - ### [RADIOCOVER](radiocover) 31 | Radio link coverage map. 32 | 33 | ## Graphics 34 | - ### [COMPROSE](comprose) 35 | Compass rose plot. 36 | 37 | - ### [ARROWS](arrows) 38 | Generalized 2-D arrows plot. 39 | 40 | - ### [POLARMAP](polarmap) 41 | Polarized color map. 42 | 43 | 44 | ## Author 45 | **François Beauducel**, [IPGP](www.ipgp.fr), [beaudu](https://github.com/beaudu), beauducel@ipgp.fr 46 | 47 | ## Documentation 48 | All functions contain in-line help for syntax and examples. 49 | -------------------------------------------------------------------------------- /arrows/README.md: -------------------------------------------------------------------------------- 1 | # Generalized 2-D arrows plot 2 | 3 | ## arrows.m 4 | 5 | ![](arrows_example.png) 6 | 7 | This little function could be an alternative to other existing arrow plot functions, since it has very simple, vectorized and effective coding. In fact, a set arrows are all plotted using a single patch command! The function also accepts any standard patch parameter/value pair like `'FaceColor'`, `'EdgeColor'`, `'LineWidth'`, etc. 8 | 9 | It can be used to plot a personalized arrow at coordinates X,Y with length L and azimuth AZ, or any of these arguments can be vector or matrix, like QUIVER function, to plot multiple arrows. The option `'Cartesian'` allows defining U and V components instead of polar L and AZ. The option `'Loop'` will plot a loop arrow of radius R and offset azimuth AZ. 10 | 11 | Arrow's aspect ratio, head and segment line shapes are configurable with 4 parameters: head width, head length, head inside length and segment line width, all normalized to arrow's length. See the upper part of examples figure. 12 | 13 | It is also possible to vary only the arrow length with fixed head and line width, using a reference length. The examples figure shows the effect of this option: upper arrows use a reference length, while lower arrows do not. 14 | 15 | Type `doc arrows` for a full documentation. 16 | 17 | ## Examples 18 | ```matlab 19 | arrows(0,0,1,45,'FaceColor','none','LineWidth',3) 20 | ``` 21 | 22 | ```matlab 23 | arrows(1,0,1,0,[.2,.4,.2,.02]) 24 | ``` 25 | 26 | ```matlab 27 | arrows(0,0,-1,45,'Loop') 28 | ``` 29 | 30 | ```matlab 31 | [xx,yy] = meshgrid(1:10); 32 | arrows(xx,yy,rand(size(xx)),360*rand(size(xx))) 33 | ``` 34 | 35 | ## Author 36 | **François Beauducel**, [IPGP](www.ipgp.fr), [beaudu](https://github.com/beaudu), beauducel@ipgp.fr 37 | 38 | ## Documentation 39 | Type 'doc arrows' for help and syntax. 40 | -------------------------------------------------------------------------------- /arrows/arrows.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/arrows/arrows.m -------------------------------------------------------------------------------- /arrows/arrows_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/arrows/arrows_example.png -------------------------------------------------------------------------------- /comprose/README.md: -------------------------------------------------------------------------------- 1 | # Compass rose plot 2 | 3 | ## comprose.m 4 | 5 | This little function draws a compass rose with optional Cardinal directions with basic properties: 6 | - 1, 4, 8, or 16-points 7 | - position X,Y on current axis 8 | - size (in axis unit) 9 | - azimuth (in degrees) 10 | - accepts any additional patch param/value pairs, like 'FaceColor', 'EdgeColor', 'LineWidth', ... 11 | - displays Cardinal point names (optional) 12 | 13 | ## Examples 14 | ```matlab 15 | comprose(0,0,8,.5,10) 16 | ``` 17 | 18 | ```matlab 19 | comprose(2,-1,1,2,0,20,'LineWidth',2,'FaceColor',.5*[1,1,1]) 20 | ``` 21 | 22 | ```matlab 23 | h = comprose(1,2.5,16,1,-10); 24 | set(h(:,1),'FaceColor',[0,.5,.5],'EdgeColor',.5*[1,1,1]) 25 | set(h(:,2),'FaceColor',.9*[1,1,1]) 26 | ``` 27 | 28 | ![](comprose_example.png) 29 | 30 | ## Author 31 | **François Beauducel**, [IPGP](www.ipgp.fr), [beaudu](https://github.com/beaudu), beauducel@ipgp.fr 32 | 33 | ## Documentation 34 | Type 'doc comprose' for help and syntax. 35 | 36 | -------------------------------------------------------------------------------- /comprose/comprose.m: -------------------------------------------------------------------------------- 1 | function ho=comprose(x,y,n,w,az,varargin) 2 | %COMPROSE Compass rose plot 3 | % 4 | % COMPROSE(X,Y,N,W,AZ) adds a compass rose on current axis located at 5 | % position X,Y with N points (N is 1, 4, 8 or 16), width W (radius) 6 | % and North pointing to azimuth AZ (in degree, AZ = 0 means an arrow 7 | % pointing to positive Y-axis direction, rotating clockwise). 8 | % 9 | % COMPROSE(...,FS) adds Cardinal directions with font size FS. 10 | % 11 | % COMPROSE(...,'param1',value1,'param2',value2,...) specifies any 12 | % additionnal properties of the Patch using standard parameter/value 13 | % pairs. Note that 'FaceColor' concerns only the default black-filled 14 | % parts of the drawing. 15 | % 16 | % H=COMPROSE(...) returns a Nx3 matrix of object handles: first column 17 | % addresses solid filled patches, second column for white patches, third 18 | % column for Cardinal direction text. 19 | % 20 | % Examples: 21 | % comprose(0,0,8,.5,10) 22 | % 23 | % comprose(2,-1,1,2,0,20,'LineWidth',2,'FaceColor',.5*[1,1,1]) 24 | % 25 | % h = comprose(1,2.5,16,1,-10); 26 | % set(h(:,1),'FaceColor',[0,.5,.5],'EdgeColor',.5*[1,1,1]) 27 | % set(h(:,2),'FaceColor',.9*[1,1,1]) 28 | % 29 | % See also PATCH. 30 | % 31 | % Author: Francois Beauducel 32 | % Created: 2012-06-24 33 | % Updated: 2012-07-01 34 | 35 | % Copyright (c) 2012, François Beauducel, covered by BSD License. 36 | % All rights reserved. 37 | % 38 | % Redistribution and use in source and binary forms, with or without 39 | % modification, are permitted provided that the following conditions are 40 | % met: 41 | % 42 | % * Redistributions of source code must retain the above copyright 43 | % notice, this list of conditions and the following disclaimer. 44 | % * Redistributions in binary form must reproduce the above copyright 45 | % notice, this list of conditions and the following disclaimer in 46 | % the documentation and/or other materials provided with the distribution 47 | % 48 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 49 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 50 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 51 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 52 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 53 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 54 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 55 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 56 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 57 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 58 | % POSSIBILITY OF SUCH DAMAGE. 59 | 60 | if nargin < 5 61 | error('Not enough input arguments.') 62 | end 63 | 64 | if nargin > 5 & isnumeric(varargin{1}) 65 | fs = varargin{1}; 66 | varargin = varargin(2:end); 67 | else 68 | fs = 0; 69 | end 70 | 71 | if ~isnumeric(x) | ~isnumeric(y) | ~isnumeric(n) | ~isnumeric(w) | ~isnumeric(az) 72 | error('X,Y,N,W, and AZ must be numeric.') 73 | end 74 | 75 | if all(n ~= [1,4,8,16]) 76 | error('N must be equal to 1, 4, 8 or 16.') 77 | end 78 | 79 | hh = []; 80 | 81 | for k = n:-1:1 82 | h = branch(x,y,k,w,az,fs,varargin{:}); 83 | hh = [hh;h]; 84 | end 85 | 86 | if nargout > 0 87 | ho = hh; 88 | end 89 | 90 | 91 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 92 | function h = branch(x,y,k,w,az,fs,varargin) 93 | 94 | cpt = {'N','E','S','W','NE','SE','SW','NW', ... 95 | 'NNE','ENE','ESE','SSE','SSW','WSW','WNW','NNW'}; 96 | cph = {'center','left','center','right','left','left','right','right', ... 97 | 'left','left','left','left','right','right','right','right'}; 98 | cpv = {'bottom','middle','top','middle','bottom','top','top','bottom', ... 99 | 'bottom','bottom','top','top','top','top','bottom','bottom'}; 100 | 101 | if k <= 4 102 | a = (k-1)*90 + az; 103 | else 104 | if k <= 8 105 | a = (k-8)*90 - 45 + az; 106 | w = w*.8; 107 | fs = fs*.6; 108 | else 109 | a = (k-16)*45 - 22.5 + az; 110 | w = w*.6; 111 | fs = fs*.4; 112 | end 113 | end 114 | 115 | r = .15; 116 | xx = w*[0 0 r]; 117 | yy = w*[0 1 r]; 118 | x1 = xx*cosd(a) + yy*sind(a); 119 | y1 = -xx*sind(a) + yy*cosd(a); 120 | x2 = -xx*cosd(a) + yy*sind(a); 121 | y2 = xx*sind(a) + yy*cosd(a); 122 | h(1) = patch(x1 + x,y1 + y,'k',varargin{:}); 123 | h(2) = patch(x2 + x,y2 + y,'k',varargin{:},'FaceColor','w'); 124 | 125 | % Cardinal points 126 | xp = 1.03*x1(2) + x; 127 | yp = 1.03*y1(2) + y; 128 | h(3) = text(xp,yp,cpt{k}, ... 129 | 'HorizontalAlignment',cph{k},'VerticalAlignment',cpv{k},'Rotation',-az); 130 | if fs > 0 131 | set(h(3),'FontSize',fs','FontWeight','bold') 132 | else 133 | set(h(3),'Visible','off') 134 | end 135 | 136 | -------------------------------------------------------------------------------- /comprose/comprose_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/comprose/comprose_example.png -------------------------------------------------------------------------------- /dem/README.md: -------------------------------------------------------------------------------- 1 | # Shaded relief image plot (digital elevation model) 2 | 3 | ## dem.m 4 | This function plots regular grids of elevation X,Y,Z in a more efficient manner than SURFL Matlab's function, because it recomputes lighting and displays result as shaded color flat RGB image. It uses also median-style filter to deal with min/max values of elevations and gradient, and proposes two specific colormaps "landcolor" and "seacolor". 5 | 6 | Color mapping and lighting parameters can be changed from default values. In addition, several options are available: `'cartesian'` to add decimal axis, `'latlon'` to add geographical axis (GMT-like), `'legend'` for an automatic scaling legend, `'lake'` for automatic flat area color-filling and `'interp'` to fill the novalue gaps... 7 | 8 | This may be useful to produce high-quality and moderate-size Postscript image adapted for publication. 9 | 10 | ## Examples 11 | 12 | Moon North Pole using the bone colormap (`'colormap','bone'`), single light source northwest (`'azimuth',-45`) and high contrast lighting using 0.2% median filter (`'lcut',.2`). DEM source: raster LRO/LOLA LTVT. 13 | 14 | ![](dem_example_moon_bone.png) 15 | 16 | Indonesia archipelago using default land/sea colormaps, lower saturation value (`'saturation',0.5`) and lat/lon axis basemap (`'latlon'`). DEM source: raster NOAA/NGDC ETOPO1. 17 | 18 | ![](dem_example_indonesia.png) 19 | 20 | Soufrière of Guadeloupe volcano lava dome: 1-m resolution containing NaN values, two light sources (`'azimuth',[-45,45]` which is the default), cartesian basemap axes (`'cartesian'`) and legend scales (`'legend'`). DEM source: OVSG-IPGP/SCIAC 2001. 21 | 22 | ![](dem_example_dome.png) 23 | 24 | Comparison of the two available shading methods on the Guadeloupe archipelago and Piton de la Fournaise volcano using roma reversed colormap (`'landcolor',flipud(roma)`) and a single light source (`'azimuth',-45`): *(left)* uses the default light method (`'shading','light'`), *(right)* stack/transparency method (`'shading','stack'`) with default opacity value of 50% (`'opacity',.5`). DEM source: SRTM/NASA 2001. 25 | 26 | ![](dem_example_guadeloupe.png) 27 | 28 | ## Author 29 | **François Beauducel**, [IPGP](www.ipgp.fr), [beaudu](https://github.com/beaudu), beauducel@ipgp.fr 30 | 31 | ## Documentation 32 | See "doc dem" for syntax, examples and help. See also the [READHGT](../readhgt/) function from same author. 33 | -------------------------------------------------------------------------------- /dem/dem.m: -------------------------------------------------------------------------------- 1 | function varargout=dem(x,y,z,varargin) 2 | %DEM Shaded relief image plot 3 | % 4 | % DEM(X,Y,Z) plots the Digital Elevation Model defined by X and Y 5 | % coordinate vectors and elevation matrix Z, as a lighted image using 6 | % specific "landcolor" and "seacolor" colormaps. DEM uses IMAGESC 7 | % function which is much faster than SURFL when dealing with large 8 | % high-resolution DEM. It produces also high-quality and moderate-size 9 | % Postscript image adapted for publication. 10 | % 11 | % DEM(X,Y,Z,'Param1',Value1,'Param2',Value2,...) specifies options or 12 | % parameter/value couple (case insensitive): 13 | % 14 | % [H,I] = DEM(...); returns graphic handle H and optional illuminated 15 | % image as I, a MxNx3 matrix (if Z is MxN and DECIM is 1). 16 | % 17 | % I = DEM(...,'noplot') returns a structure I containing fields x, y, z, 18 | % and illuminated image .rgb without producing a graph on current figure. 19 | % 20 | % 21 | % --- Lighting options --- 22 | % 23 | % 'Shading', 'light' (default) | 'stack' 24 | % Shading method to combine the relief image and shadow mask: 25 | % 'light' is the default and initial method of dem.m (changing the 26 | % lightness intensity), 27 | % 'stack' is the common transparency method (stacking using an 28 | % opacity value). 29 | % 30 | % 'Azimuth',A 31 | % Light azimuth in degrees clockwise relative to North. Accepts 32 | % a vector for multiple lights. Default is A = [-45,45] for a 33 | % combined north-west and north-east illumination. 34 | % 35 | % 'Elevation',E 36 | % Light elevation (or altitude) (in degrees from the horizon level). 37 | % Default value is E = 45. 38 | % 39 | % 'Opacity',O 40 | % Opacity value (between 0 and 1) of the relief image when using the 41 | % 'stack' shading method. Default value is O = 0.5. 42 | % 43 | % 'Contrast',C 44 | % Light contrast, as the exponent of the gradient value: 45 | % C = 1 for linear contrast (default), 46 | % C = 0 to remove lighting, 47 | % C = 0.5 for moderate lighting, 48 | % C = 2 or more for stronger contrast. 49 | % 50 | % 'LCut',LC 51 | % Lighting scale saturation cut with a median-style filter in % of 52 | % elements, such as LC% of maximum gradient values are ignored (0 is 53 | % default for full scale gradient). 54 | % 55 | % 'km' 56 | % Stands that X and Y coordinates are in km instead of m (default). 57 | % This allows correct lighting. Ignored if LATLON option is used. 58 | % 59 | % 60 | % --- Elevation colorscale options --- 61 | % 62 | % 'ZLim',[ZMIN,ZMAX] 63 | % Fixes min and max elevation values for colormap. Use NaN to keep 64 | % real min and/or max data values. 65 | % 66 | % 'ZCut',ZC 67 | % Median-style filter to cut extremes values of Z (in % of elements), 68 | % such that ZC% of most min/max elevation values are ignored in the 69 | % colormap application: 70 | % ZC = 0.5 is default, 71 | % ZC = 0 for full scale. 72 | % 73 | % 74 | % --- "No Value" elevation options --- 75 | % 76 | % 'NoValue',NOVALUE 77 | % Defines the values that will be replaced by NaN. Note that values 78 | % equal to minimum of Z class are automatically detected as NaN 79 | % (e.g., -32768 for int16 class). 80 | % 81 | % 'NaNColor',[R,G,B] 82 | % Sets the RGB color for NaN/NoValue pixels (default is a dark gray). 83 | % Note that your must specify a valid 3-scalar vector (between 0 and 84 | % 1); color characters like 'w' or 'k' are not allowed, use [1,1,1] 85 | % or [0,0,0] instead. 86 | % 87 | % 'Interp' 88 | % Interpolates linearly all NaN values (fills the gaps using linear 89 | % triangulation), using an optimized algorithm. 90 | % 91 | % 92 | % --- Colormap options --- 93 | % 94 | % 'LandColor',LMAP 95 | % Uses LMAP colormap instead of default (landcolor, if exists or 96 | % jet) for Z > 0 elevations. 97 | % 98 | % 'SeaColor',SMAP 99 | % Sets the colormap used for Z <= 0 elevations. Default is seacolor 100 | % (if exists) or single color [0.7,0.9,1] (a light cyan) to simulate 101 | % sea color. 102 | % 103 | % 'ColorMap',CMAP 104 | % Uses CMAP colormap for full range of elevations, instead of default 105 | % land/sea. This option overwrites LANDCOLOR/SEACOLOR options. 106 | % 107 | % 'Lake' 108 | % Detects automatically flat areas different from sea level (non-zero 109 | % elevations) and colors them as lake surfaces. 110 | % 111 | % 'LakeZmin',ZMIN 112 | % Activates the 'lake' option only above ZMIN elevations. For 113 | % example, use 'lakezmin',0 to limit lake detection on land. 114 | % 115 | % 'Saturation',N 116 | % Changes the whole image color saturation by a factor of N. 117 | % 118 | % 'GrayScale' 119 | % Converts the used colormap(s) to grayscale (colour-blind). 120 | % 121 | % 'Watermark',N 122 | % Makes the whole image lighter by a factor of N. 123 | % 124 | % 125 | % --- Basemap and scale options --- 126 | % 127 | % 'Legend' 128 | % Adds legends to the right of graph: elevation scale (colorbar) 129 | % and a distance scale (in km). 130 | % 131 | % 'Cartesian' 132 | % Plots classic basemap-style axis, considering coordinates X and Y 133 | % as cartesian in meters. Use parameter "km' for X/Y in km. 134 | % 135 | % 'LatLon' 136 | % Plots geographic basemap-style axis in deg/min/sec, considering 137 | % coordinates X as longitude and Y as latitude. Axis aspect ratio 138 | % will be adjusted to approximatively preserve distances (this is 139 | % not a real projection!). This overwrites ZRatio option. 140 | % 141 | % 'AxisEqual', 'auto' (default) | 'manual' | 'off' 142 | % When 'Cartesian' or 'LatLon' option is used, automatic axes scaling 143 | % is applied to respect data aspect ratio. Default mode is 'auto' and 144 | % uses AXIS EQUAL and DASPECT functions. The 'manual' mode modifies 145 | % axes width or height with respect to the paper size in order to 146 | % produce correct data scaling at print (but not necessarily at 147 | % screen). The 'off' mode disables any scaling. 148 | % 149 | % Additionnal options for basemap CARTESIAN, LATLON, and LEGEND: 150 | % 151 | % 'BorderWidth',BW 152 | % Border width of the basemap axis, in % of axis height. Default is 153 | % BW = 1%. 154 | % 155 | % 'XTick',DX 156 | % 'YTick',DY 157 | % X and Y Tick length (same unit as X and Y). Default is automatic. 158 | % Tick labels are every 2 ticks. 159 | % 160 | % 'FontSize',FS 161 | % Font size for X and Y tick labels. Default is FS = 10. 162 | % 163 | % 'FontBold' 164 | % Font weight bold for tick labels. 165 | % 166 | % 'Position',P 167 | % Position of the tick labels: 'southwest' (default), 'southeast', 168 | % 'northwest','northeast' 169 | % 170 | % 'ZUnit',ZU 171 | % Sets the elevation unit as string ZU, used when min/max values are 172 | % indicated on colorbar legend (default is 'm'). 173 | % 174 | % --- Decimation options --- 175 | % 176 | % For optimization purpose, DEM will automatically decimate data to limit 177 | % to a total of 1500x1500 pixels images. To avoid this, use following 178 | % options, but be aware that large grids may require huge computer 179 | % ressources or induce disk swap or memory errors. 180 | % 181 | % 'Decim',N 182 | % Decimates matrix Z at 1/N times of the original sampling. 183 | % If N < 0, oversamples at -N rate. 184 | % 185 | % 'NoDecim' 186 | % Forces full resolution of Z, no decimation (N =1). 187 | % 188 | % 189 | % 190 | % --- Informations --- 191 | % 192 | % Colormaps are Mx3 RGB matrix so it is easy to modify contrast 193 | % (CMAP.^N), set darker (CMAP/N), lighter (1 - 1/N + CMAP/N), inverse 194 | % it (flipud(CMAP)), etc... 195 | % 196 | % To get free worldwide topographic data (SRTM), see READHGT function. 197 | % 198 | % For backward compatibility, the former syntax is still accepted: 199 | % DEM(X,Y,Z,OPT,CMAP,NOVALUE,SEACOLOR) where OPT = [A,C,LC,ZMIN,ZMAX,ZC], 200 | % also option aliases DEC, DMS and SCALE, but there is no argument 201 | % checking. Please prefer the param/value syntax. 202 | % 203 | % Author: François Beauducel 204 | % 205 | % Acknowledgments: Éric Gayer 206 | % 207 | % Created: 2007-05-17 in Guadeloupe, French West Indies 208 | % Updated: 2022-11-26 209 | 210 | % History: 211 | % [2022-11-26] v3.2 212 | % - fix an issue with 'grayscale' option 213 | % - allows duplicate arguments (takes the last one) 214 | % [2022-07-26] v3.1 215 | % - minor fix for Octave compatibility 216 | % [2022-07-21] v3.0 217 | % - new option 'shading' (stack method with opacity factor) 218 | % - new option 'elevation' (light elevation for stack shading) 219 | % - lighting with multiple source azimuth 220 | % - graphic improvement of basemap axes corners 221 | % [2021-01-08] v2.11 222 | % - minor optimization for 'interp' option 223 | % [2020-11-29] v2.10 224 | % - new option 'grayscale' 225 | % [2020-11-25] v2.9 226 | % - fix a possible issue with saturation under Matlab < 2014 227 | % [2020-06-01] v2.8 228 | % - new option 'saturation' to change colormaps 229 | % - tick label decimals with smaller font size 230 | % [2019-06-17] v2.7 231 | % - fix an issue for single RGB color in sea/land color option 232 | % [2017-03-29] v2.6 233 | % - fix in 'lakezmin' option (thanks to Mustafa �omo?lu) 234 | % [2017-01-09] v2.5 235 | % - new option 'lakezmin' to limit lake detection 236 | % [2016-12-21] v2.4 237 | % - improves the colormap splitting between land and sea 238 | % [2016-04-19] v2.3 239 | % - major update (thanks to mas Wiwit) 240 | % [2016-01-31] v2.2 241 | % - adds option 'Position' for tick labels 242 | % [2015-08-22] v2.1 243 | % - minor fix (former versions of Matlab compatibility) 244 | % [2015-08-19] v2.0 245 | % - image is now 100% true color (including the legend colorbar), 246 | % thus completely independent from the figure colormap 247 | % [2014-10-14] 248 | % - 'decim' option allows oversampling (negative value) 249 | % [2014-06-06] 250 | % - improves backward compatibility (adds strjoin subfunction) 251 | % [2014-03-18] 252 | % - adds new axisequal option 253 | % [2013-03-11] 254 | % - new options: 'km', 'watermark', 'fontsize', 'bordersize' 255 | % - improve legend colorbar 256 | % - all options now passed as param/value 257 | % [2013-01-14] 258 | % - improved light rendering (using surface normals instead of gradient) 259 | % - improved 'lake' detection algorithm 260 | % - new 'nancolor' option to set NaN color 261 | % - adds a length scale with 'dec' option 262 | % - minor code improvements 263 | % [2013-01-07] 264 | % - adds 'interp' option (fill the gaps) 265 | % - adds 'seacolor' colormap for negative elevations (bathymetry) 266 | % [2013-01-02] 267 | % - adds a 'lake' option 268 | % - minor bug correction 269 | % [2012-09-26] 270 | % - now accepts row/column vectors for X and/or Y. 271 | % [2012-05-29] 272 | % - adds basemap-style axis in decimal or lat/lon modes 273 | % - adds elevation and distance scales 274 | % [2012-05-18] 275 | % - new landcolor.m colormap function 276 | % - new arguments to control colormap scaling 277 | % - median-style filters for light and colormap 278 | % [2012-04-26] 279 | % - Optimizations: adds a decimation for large DEM grids. 280 | 281 | % 282 | % Copyright (c) 2007-2022, Fran�ois Beauducel, covered by BSD License. 283 | % All rights reserved. 284 | % 285 | % Redistribution and use in source and binary forms, with or without 286 | % modification, are permitted provided that the following conditions are 287 | % met: 288 | % 289 | % * Redistributions of source code must retain the above copyright 290 | % notice, this list of conditions and the following disclaimer. 291 | % * Redistributions in binary form must reproduce the above copyright 292 | % notice, this list of conditions and the following disclaimer in 293 | % the documentation and/or other materials provided with the distribution 294 | % 295 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 296 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 297 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 298 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 299 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 300 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 301 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 302 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 303 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 304 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 305 | % POSSIBILITY OF SUCH DAMAGE. 306 | 307 | if nargin < 3 308 | error('Not enough input arguments.'); 309 | end 310 | 311 | holdon = ishold; 312 | 313 | degkm = 6378*pi/180; % one latitude degree in km 314 | sea_color = [.7,.9,1]; % default sea color (light cyan) 315 | grey = 0.2*[1,1,1]; % a dark gray 316 | 317 | 318 | % ------------------------------------------------------------------------- 319 | % --- Manage input arguments 320 | 321 | % number of arguments param/value 322 | nargs = 0; 323 | 324 | if ~isnumeric(x) || ~isnumeric(y) || ~isnumeric(z) 325 | error('X,Y and Z must be numeric.') 326 | end 327 | 328 | if all(size(x) ~= 1) || all(size(y) ~= 1) 329 | error('X and Y must be vectors, not matrix.') 330 | end 331 | 332 | if length(x) ~= size(z,2) || length(y) ~= size(z,1) 333 | error('If Z has a size of [M,N], X must have a length of N, and Y a length of M.') 334 | end 335 | 336 | if size(z,3) == 3 337 | rgb = true; 338 | else 339 | rgb = false; 340 | end 341 | 342 | % OPTIONS and PARAM/VALUE arguments 343 | 344 | % SHADING param/value 345 | [s,shading] = checkparam(varargin,'shading',@ischar,{'light','stack'}); 346 | nargs = nargs + 2; 347 | if s==0 348 | shading = 'light'; % default 349 | end 350 | 351 | % OPACITY param/value 352 | [s,opacity] = checkparam(varargin,'opacity',@isscalar); 353 | nargs = nargs + 2; 354 | if s==0 355 | opacity = .5; % default 356 | end 357 | 358 | % AZIMUTH param/value 359 | [s,az] = checkparam(varargin,'azimuth',@isvec); 360 | nargs = nargs + 2; 361 | if s==0 362 | az = [-45,45]; % default 363 | end 364 | 365 | % ZENITH param/value 366 | [s,el] = checkparam(varargin,'elevation',@isscalar); 367 | nargs = nargs + 2; 368 | if s==0 369 | el = 45; % default 370 | end 371 | 372 | % CONTRAST param/value 373 | [s,ct] = checkparam(varargin,'contrast',@isscalar); 374 | nargs = nargs + 2; 375 | if s 376 | ct = abs(ct); 377 | else 378 | ct = 1; % default 379 | end 380 | 381 | % LCUT param/value 382 | [s,lcut] = checkparam(varargin,'lcut',@isperc); 383 | nargs = nargs + 2; 384 | if s==0 385 | lcut = 0; % default 386 | end 387 | 388 | % NOVALUE param/value 389 | [s,novalue] = checkparam(varargin,'novalue',@isscalar); 390 | nargs = nargs + 2; 391 | if s==0 392 | % default: min value for integer class / NaN for float 393 | S = whos('z'); 394 | if strfind(S.class,'int') 395 | novalue = intmin(S.class); 396 | else 397 | novalue = NaN; 398 | end 399 | end 400 | 401 | % NANCOLOR param/value 402 | [s,novalue_color] = checkparam(varargin,'nancolor',@isrgb); 403 | nargs = nargs + 2; 404 | if s==0 405 | novalue_color = grey; % default 406 | end 407 | 408 | % LANDCOLOR param/value 409 | [s,cland] = checkparam(varargin,'landcolor',@isrgb); 410 | nargs = nargs + 2; 411 | if s==0 412 | % default: landcolor or jet 413 | if exist('landcolor','file') 414 | cland = landcolor.^1.3; 415 | else 416 | cland = jet(256); 417 | end 418 | end 419 | 420 | % SEACOLOR param/value 421 | [s,csea] = checkparam(varargin,'seacolor',@isrgb); 422 | nargs = nargs + 2; 423 | if s==0 424 | % default: seacolor or single color 425 | if exist('seacolor','file') 426 | csea = seacolor; 427 | else 428 | csea = sea_color; 429 | end 430 | end 431 | 432 | % COLORMAP param/value 433 | [s,cmap] = checkparam(varargin,'colormap',@isrgb); 434 | nargs = nargs + 2; 435 | if s 436 | cland = []; 437 | csea = []; 438 | else 439 | % default 440 | cmap = cland; 441 | end 442 | 443 | % ZLIM param/value 444 | [s,zmm] = checkparam(varargin,'zlim',@isvec); 445 | nargs = nargs + 2; 446 | if s 447 | zmin = min(zmm); 448 | zmax = max(zmm); 449 | else 450 | zmin = NaN; % default 451 | zmax = NaN; % default 452 | end 453 | 454 | % ZCUT param/value 455 | [s,zcut] = checkparam(varargin,'zcut',@isperc); 456 | nargs = nargs + 2; 457 | if s==0 458 | zcut = .5; % default 459 | end 460 | 461 | % ZRATIO param/value 462 | [s,zratio] = checkparam(varargin,'zratio',@isscalar); 463 | nargs = nargs + 2; 464 | if s==0 465 | zratio = 1; % default 466 | end 467 | 468 | % SATURATION param/value 469 | [s,csat] = checkparam(varargin,'saturation',@isscalar); 470 | nargs = nargs + 2; 471 | if s 472 | csat = abs(csat); 473 | else 474 | csat = 1; % default 475 | end 476 | 477 | % WATERMARK param/value 478 | [s,wmark] = checkparam(varargin,'watermark',@isscalar); 479 | nargs = nargs + 2; 480 | if s 481 | wmark = abs(wmark); 482 | else 483 | wmark = 0; % default 484 | end 485 | 486 | % DECIM param/value and NODECIM option 487 | [s,decim] = checkparam(varargin,'decim',@isscalar); 488 | if s 489 | decim = round(decim); 490 | nargs = nargs + 2; 491 | else 492 | decim = any(strcmpi(varargin,'nodecim')); % default 493 | nargs = nargs + 1; 494 | end 495 | 496 | % LAKEZMIN param/value option 497 | [s,lakezmin] = checkparam(varargin,'lakezmin',@isscalar); 498 | if s 499 | lake = 1; 500 | nargs = nargs + 2; 501 | else 502 | lake = 0; 503 | lakezmin = NaN; 504 | end 505 | 506 | % FONTSIZE param/value 507 | [s,fs] = checkparam(varargin,'fontsize',@isscalar); 508 | nargs = nargs + 2; 509 | if s==0 510 | fs = 10; % default 511 | end 512 | 513 | % BORDERWIDTH param/value 514 | [s,bw] = checkparam(varargin,'borderwidth',@isperc); 515 | nargs = nargs + 2; 516 | if s==0 517 | bw = 1; % default 518 | end 519 | 520 | % XTICK param/value 521 | [s,ddx] = checkparam(varargin,'xtick',@isscalar); 522 | nargs = nargs + 2; 523 | if s==0 524 | ddx = 0; % default (automatic) 525 | end 526 | 527 | % YTICK param/value 528 | [s,ddy] = checkparam(varargin,'ytick',@isscalar); 529 | nargs = nargs + 2; 530 | if s==0 531 | ddy = 0; % default (automatic) 532 | end 533 | 534 | % POSITION param/value 535 | [s,tpos] = checkparam(varargin,'position',@ischar,{'southwest','southeast','northwest','northeast'}); 536 | nargs = nargs + 2; 537 | if s==0 538 | tpos = 'southwest'; % default 539 | end 540 | 541 | % ZUNIT param/value 542 | [s,zunit] = checkparam(varargin,'zunit',@ischar); 543 | nargs = nargs + 2; 544 | if s==0 545 | zunit = 'm'; % default 546 | end 547 | 548 | % AXISEQUAL param/value 549 | [s,axeq] = checkparam(varargin,'axisequal',@ischar,{'auto','manual','off'}); 550 | nargs = nargs + 2; 551 | if s==0 || ~any(strcmpi(axeq,{'manual','off'})) 552 | axeq = 'auto'; % default (automatic) 553 | end 554 | 555 | % CROP param/value 556 | [s,crop] = checkparam(varargin,'crop',@isvec,4); 557 | nargs = nargs + 2; 558 | 559 | % CLRGB param/value 560 | [s,clrgb] = checkparam(varargin,'CLRGB',@isrgb); 561 | nargs = nargs + 2; 562 | if s==0 563 | clrgb = .5*ones(1,3); % default (mid-grey) 564 | end 565 | 566 | % CLLEVEL param/value 567 | [s,cllevel] = checkparam(varargin,'CLLevel',@isvec,1:2); 568 | nargs = nargs + 2; 569 | if s==0 570 | cllevel = [0 0]; % default (auto) 571 | end 572 | 573 | % options without argument value 574 | km = any(strcmpi(varargin,'km')); 575 | dec = any(strcmpi(varargin,'cartesian') | strcmpi(varargin,'dec')); 576 | dms = any(strcmpi(varargin,'latlon') | strcmpi(varargin,'dms')); 577 | kmscale = any(strcmpi(varargin,'kmscale')); 578 | scale = any(strcmpi(varargin,'legend') | strcmpi(varargin,'scale')); 579 | inter = any(strcmpi(varargin,'interp')); 580 | lake = any(strcmpi(varargin,'lake')) || lake; 581 | fbold = any(strcmpi(varargin,'fontbold')); 582 | noplot = any(strcmpi(varargin,'noplot')); 583 | clines = any(strcmpi(varargin,'contourlines')); 584 | gscale = any(strcmpi(varargin,'grayscale')); 585 | 586 | 587 | % for backward compatibility (former syntax)... 588 | nargs = nargs + dec + dms + scale + kmscale + inter + lake + km + fbold + noplot + clines + gscale; 589 | 590 | if (nargin - nargs) > 3 && ~isempty(varargin{1}) 591 | opt = varargin{1}; 592 | if ~isnumeric(opt) 593 | error('OPT = [A,C,S,ZMIN,ZMAX,ZCUT] argument must be numeric.'); 594 | end 595 | if ~isempty(opt) 596 | az = opt(1); 597 | end 598 | if length(opt) > 1 599 | ct = opt(2); 600 | end 601 | if length(opt) > 2 602 | lcut = opt(3); 603 | end 604 | if length(opt) > 4 605 | zmin = opt(4); 606 | zmax = opt(5); 607 | end 608 | if length(opt) > 5 609 | zcut = opt(6); 610 | end 611 | end 612 | 613 | if (nargin - nargs) > 4 && ~isempty(varargin{2}) 614 | cmap = varargin{2}; 615 | csea = []; 616 | end 617 | 618 | if (nargin - nargs) > 5 && ~isempty(varargin{3}) 619 | novalue = varargin{3}; 620 | end 621 | 622 | if (nargin - nargs) > 6 && ~isempty(varargin{4}) 623 | csea = varargin{4}; 624 | end 625 | 626 | 627 | % further test of input arguments 628 | if dms && any(abs(y) > 91) 629 | error('With LATLON option Y must be in valid latitudes interval (decimal degrees).') 630 | end 631 | 632 | if km 633 | zratio = 1000; 634 | end 635 | 636 | 637 | % ------------------------------------------------------------------------- 638 | % --- Pre-process DEM data 639 | 640 | % crops data if needed 641 | if numel(crop)==4 642 | fprintf('DEM: crops original data from [%g,%g,%g,%g] to [%g,%g,%g,%g]...\n', ... 643 | min(x(:)),max(x(:)),min(y(:)),max(y(:)),crop); 644 | kx = find(x >= crop(1) & x <= crop(2)); 645 | ky = find(y >= crop(3) & y <= crop(4)); 646 | x = x(kx); 647 | y = y(ky); 648 | z = z(ky,kx,:); 649 | end 650 | 651 | % decimates data to avoid disk swap/out of memory... 652 | nmax = 1500; 653 | if decim 654 | n = decim; 655 | else 656 | n = ceil(sqrt(numel(z))/nmax); 657 | end 658 | if n > 1 659 | x = x(1:n:end); 660 | y = y(1:n:end); 661 | z = z(1:n:end,1:n:end,:); 662 | fprintf('DEM: data has been decimated by a factor of %d...\n',n); 663 | end 664 | 665 | z = double(z); % necessary for most of the following calculations... 666 | z(z==novalue) = NaN; 667 | 668 | if isempty(csea) 669 | k = (z~=0 & ~isnan(z)); 670 | else 671 | k = ~isnan(z); 672 | end 673 | 674 | if isnan(zmin) 675 | zmin = nmedian(z(k),zcut/100); 676 | end 677 | if isnan(zmax) 678 | zmax = nmedian(z(k),1 - zcut/100); 679 | end 680 | dz = zmax - zmin; 681 | 682 | if decim && n < 0 683 | xi = linspace(x(1),x(end),-n*length(x)); 684 | yi = linspace(y(1),y(end),-n*length(y))'; 685 | [xx,yy] = meshgrid(xi,yi); 686 | z = interp2(x,y,z,xx,yy,'*cubic'); 687 | x = xi; 688 | y = yi; 689 | fprintf('DEM: data has been oversampled by a factor of %d...\n',-n); 690 | end 691 | 692 | if ~rgb && inter 693 | z = fillgap(x,y,z); 694 | end 695 | 696 | % ------------------------------------------------------------------------- 697 | % --- Process lighting 698 | 699 | if ~rgb && dz > 0 700 | % first check if colormaps have the minimum required size 701 | if size(csea,1) < 2 702 | csea = repmat(csea,256,1); 703 | end 704 | if size(cland,1) < 2 705 | cland = repmat(cland,256,1); 706 | end 707 | % builds the colormap: concatenates seacolor and landcolor around 0 708 | % after interpolation to have exactly one color level per meter. 709 | if ~isempty(csea) 710 | % l = size(csea,1); 711 | % if zmin < 0 && zmax > 0 712 | % r = size(cland,1)*abs(zmin)/zmax/l; 713 | % cmap = cat(1,interp1(1:l,csea,linspace(1,l,ceil(l*r)),'*linear'),cland); 714 | if zmin < 0 && zmax > 0 715 | lcs = size(csea,1); 716 | lcl = size(cland,1); 717 | cmap = cat(1,interp1(1:lcs,csea,linspace(1,lcl,abs(zmin)+1),'*linear'), ... 718 | interp1(1:lcl,cland,linspace(1,lcl,abs(zmax)),'*linear')); 719 | elseif zmax <=0 720 | cmap = csea; 721 | end 722 | end 723 | 724 | % normalisation of Z using CMAP and convertion to RGB 725 | I = ind2rgb(uint16(min(max((z - zmin)/dz,0),1)*(size(cmap,1)-1)),cmap); 726 | 727 | if ct > 0 728 | dx = diff(x(1:2)); 729 | dy = diff(y(1:2)); 730 | if dms 731 | dx = dx*degkm*1000; 732 | dy = dy*degkm*1000*cosd(mean(y)); 733 | else 734 | dx = zratio*dx; 735 | dy = zratio*dy; 736 | end 737 | % computes lighting from hillshade ArcGIS algotithm 738 | shadow = hillshade(z,az,el,dx,dy,shading); 739 | 740 | % computes maximum absolute gradient (median-style), normalizes, 741 | % saturates and duplicates in 3-D matrix 742 | r = repmat(max(min(shadow/nmedian(abs(shadow),1 - lcut/100),1),-1),[1,1,3]); 743 | %r = repmat(max(min(fxy/diff(nmedian(fxy,[lcut/100,1 - lcut/100])),1),-1),[1,1,3]); 744 | %r = repmat(fxy,[1,1,3]); 745 | 746 | switch shading 747 | case 'light' 748 | % applies contrast using exponent 749 | rp = (1 - abs(r)).^ct; 750 | 751 | % darker for negative hillshade 752 | %I(r<0) = I(r<0).*(1 - abs(r(r<0))); 753 | I = I.*rp; 754 | 755 | % lighter for positive gradient 756 | I(r>0) = I(r>0) + (1 - rp(r>0)); 757 | otherwise 758 | % applies contrast using exponent 759 | rp = ((r - min(r(:)))/diff(minmax(r))).^(ct*2); 760 | % applies transparency 761 | I = opacity*I + (1-opacity)*rp; 762 | end 763 | end 764 | 765 | % set novalues / NaN to nancolor 766 | [i,j] = find(isnan(z)); 767 | if ~isempty(i) 768 | I(sub2ind(size(I),repmat(i,1,3),repmat(j,1,3),repmat(1:3,size(i,1),1))) = repmat(novalue_color,size(i,1),1); 769 | end 770 | 771 | % lake option 772 | if lake 773 | klake = islake(z); 774 | if ~isnan(lakezmin) 775 | klake(z < lakezmin) = false; % removes indexes below ZMIN 776 | end 777 | else 778 | klake = 0; 779 | end 780 | 781 | % set the seacolor (upper color) for 0 values 782 | if ~isempty(csea) 783 | [i,j] = find(z==0 | klake); 784 | if ~isempty(i) 785 | I(sub2ind(size(I),repmat(i,1,3),repmat(j,1,3),repmat(1:3,size(i,1),1))) = repmat(csea(end,:),size(i,1),1); 786 | end 787 | end 788 | 789 | txt = ''; 790 | 791 | elseif rgb 792 | I = z; 793 | txt = ''; 794 | 795 | else 796 | I = repmat(shiftdim(sea_color,-1),size(z)); 797 | cmap = repmat(sea_color,[256,1]); 798 | txt = 'Mak Byur!'; % Splash ! 799 | end 800 | 801 | % ------------------------------------------------------------------------- 802 | % --- applies saturation, grayscale and watermark to image and cmap (for legend) 803 | 804 | if gscale 805 | I = rgb2gray(I); 806 | cmap = rgb2gray(cmap); 807 | end 808 | 809 | if csat~=1 810 | I = saturation(I,csat); 811 | cmap = saturation(cmap,csat); 812 | end 813 | 814 | if wmark 815 | I = watermark(I,wmark); 816 | cmap = watermark(cmap,wmark); 817 | end 818 | 819 | if strcmpi(shading,'stack') 820 | cmap = cmap*opacity + sind(el)*(1 - opacity); 821 | end 822 | 823 | 824 | % ------------------------------------------------------------------------- 825 | % --- ends the function when 'noplot' option is on 826 | if noplot 827 | varargout{1} = struct('x',x,'y',y,'z',z,'rgb',I,'cmap',cmap); 828 | return 829 | end 830 | 831 | % ------------------------------------------------------------------------- 832 | % --- plots the RGB image 833 | hh = imagesc(x,y,I); 834 | 835 | if ~isempty(txt) 836 | text(mean(x),mean(y),txt,'Color',sea_color/4, ... 837 | 'FontWeight','bold','HorizontalAlignment','center') 838 | end 839 | 840 | orient tall; axis xy 841 | if strcmpi(axeq,'auto') 842 | axis equal 843 | end 844 | axis tight 845 | xlim = [min(x),max(x)]; 846 | ylim = [min(y),max(y)]; 847 | zlim = [min([z(z(:) ~= novalue);zmin]),max([z(z(:) ~= novalue);zmax])]; 848 | 849 | if dms 850 | % approximates X-Y aspect ratio for this latitude (< 20-m precision for 1x1� grid) 851 | xyr = cos(mean(y)*pi/180); 852 | else 853 | xyr = 1; 854 | end 855 | 856 | bw0 = max(diff(xlim)*xyr,diff(ylim))/100; 857 | bwy = bw*bw0; % Y border width = 1% 858 | bwx = bwy/xyr; % border width (in degree of longitude) 859 | 860 | 861 | % ------------------------------------------------------------------------- 862 | % --- Axis basemap style 863 | if dec || dms 864 | axis off 865 | 866 | if strcmpi(axeq,'manual') 867 | ppos = get(gcf,'PaperPosition'); 868 | apos = get(gca,'Position'); 869 | xyf = (xyr*diff(xlim)/apos(3)/ppos(3))/(diff(ylim)/apos(4)/ppos(4)); 870 | if xyf >= 1 871 | set(gca,'Position',[apos(1),apos(2),apos(3),apos(4)/xyf]); 872 | else 873 | set(gca,'Position',[apos(1),apos(2),apos(3)*xyf,apos(4)]); 874 | end 875 | end 876 | if strcmpi(axeq,'auto') 877 | if diff(xlim)*xyr <= diff(ylim) 878 | set(gca,'DataAspectRatio',[1,xyr,1]) 879 | else 880 | set(gca,'DataAspectRatio',[1/xyr,1,1]) 881 | end 882 | end 883 | 884 | if bw > 0 885 | % transparent borders with diagonal lines 886 | hold on 887 | patch(xlim([1,1,2,2]),ylim([1,2,2,1]),'k','FaceColor','none','clipping','off') 888 | patch(xlim([1,1,2,2]) + bwx*[-1,-1,1,1],ylim([1,2,2,1]) + bwy*[-1,1,1,-1],'k','FaceColor','none','clipping','off') 889 | plot(repmat(xlim(1) + bwx*[-1;0],1,2),repmat(ylim,2,1) + bwy*[-1,1;0,0],'k-','clipping','off') 890 | plot(repmat(xlim(2) + bwx*[1;0],1,2),repmat(ylim,2,1) + bwy*[-1,1;0,0],'k-','clipping','off') 891 | hold off 892 | end 893 | dlon = {'E','W'}; 894 | dlat = {'N','S'}; 895 | if fbold 896 | fw = 'bold'; 897 | else 898 | fw = 'normal'; 899 | end 900 | 901 | if ddx == 0 902 | ddx = dtick(diff(xlim),dms); 903 | ddxn = 0; 904 | else 905 | ddxn = double(ddx<0); 906 | ddx = abs(ddx); 907 | end 908 | if ddy == 0 909 | ddy = dtick(diff(ylim),dms); 910 | ddyn = 0; 911 | else 912 | ddyn = double(ddy<0); 913 | ddy = abs(ddy); 914 | end 915 | xtick = (ddx*ceil(xlim(1)/ddx)):ddx:xlim(2); 916 | for xt = xtick(1:2:end) 917 | dt = ddx - max(0,xt + ddx - xlim(2)); 918 | dt2 = ddx - max(0,xt + ddx - bwx - xlim(2)); 919 | patch(repmat(xt + [0,dt,dt2,0]',[1,2]),[ylim(1) - bwy*[0,0,1,1];ylim(2) + bwy*[0,0,1,1]]','k','clipping','off') 920 | if fs > 0 921 | if ~isempty(regexp(tpos,'north','once')) 922 | text(xt + dt*ddxn,ylim(2) + 1.2*bwy,deg2dms(xt + dt*ddxn,dlon,dec,fs),'FontSize',fs,'FontWeight',fw, ... 923 | 'HorizontalAlignment','center','VerticalAlignment','bottom'); 924 | else 925 | text(xt + dt*ddxn,ylim(1) - 1.2*bwy,deg2dms(xt + dt*ddxn,dlon,dec,fs),'FontSize',fs,'FontWeight',fw, ... 926 | 'HorizontalAlignment','center','VerticalAlignment','top'); 927 | end 928 | end 929 | end 930 | 931 | ytick = (ddy*ceil(ylim(1)/ddy)):ddy:ylim(2); 932 | for yt = ytick(1:2:end) 933 | dt = ddy - max(0,yt + ddy - ylim(2)); 934 | dt2 = ddy - max(0,yt + ddy - bwy - ylim(2)); 935 | patch([xlim(1) - bwx*[0,0,1,1];xlim(2) + bwx*[0,0,1,1]]',repmat(yt + [0,dt,dt2,0]',[1,2]),'k','clipping','off') 936 | if fs > 0 937 | if ~isempty(regexp(tpos,'east','once')) 938 | text(xlim(2) + 1.2*bwx,yt + dt*ddyn,deg2dms(yt + dt*ddyn,dlat,dec,fs),'FontSize',fs,'FontWeight',fw, ... 939 | 'HorizontalAlignment','center','VerticalAlignment','top','rotation',90); 940 | else 941 | text(xlim(1) - 1.2*bwx,yt + dt*ddyn,deg2dms(yt + dt*ddyn,dlat,dec,fs),'FontSize',fs,'FontWeight',fw, ... 942 | 'HorizontalAlignment','center','VerticalAlignment','bottom','rotation',90); 943 | end 944 | end 945 | end 946 | end 947 | 948 | % ------------------------------------------------------------------------- 949 | % --- Contour lines 950 | 951 | % contour lines 952 | if clines 953 | dz = diff(zlim); 954 | % empirical ratio between horizontal extent and elevation interval (dz) 955 | %rzh = dz/min(diff(x([1,end]))*cosd(mean(dlat)),diff(y([1,end])))/degkm/4e2; 956 | % major level lines 957 | if cllevel(1)==0 958 | dd = dtick(dz); 959 | else 960 | dd = abs(cllevel(1)); 961 | end 962 | dz0 = ceil(zlim(1)/dd)*dd:dd:floor(zlim(2)/dd)*dd; 963 | dz0(ismember(0,dz0)) = []; % eliminates 0 value 964 | % minor level lines 965 | if numel(cllevel)<2 || cllevel(2)==0 966 | dd = dtick(dz/5); 967 | else 968 | dd = abs(cllevel(2)); 969 | end 970 | dz1 = ceil(zlim(1)/dd)*dd:dd:floor(zlim(2)/dd)*dd; 971 | dz1(ismember(dz1,dz0)) = []; % eliminates minor ticks in major ticks 972 | hold on 973 | [~,h] = contour(x,y,z,[0,0,dz1],'-','Color',clrgb); 974 | set(h,'LineWidth',0.1); 975 | [cs,h] = contour(x,y,z,[0,0,dz0],'-','Color',clrgb); 976 | set(h,'LineWidth',1); 977 | if ~isempty(dz0)% && clineslabel 978 | clabel(cs,h,dz0,'Color',clrgb,'FontSize',fs/2,'FontWeight','bold', ... 979 | 'LabelSpacing',288,'Margin',fs) 980 | end 981 | hold off 982 | end 983 | 984 | % ------------------------------------------------------------------------- 985 | % --- Scales legend 986 | 987 | %wsc = diff(xlim)*0.01; 988 | wsc = bw0; 989 | xsc = xlim(2) + wsc*2 + bwx; 990 | 991 | if scale 992 | 993 | % -- elevation scale (colorbar) 994 | zscale = linspace(zmin,zmax,length(cmap))'; 995 | yscale = linspace(0,diff(ylim)/2,length(cmap)); 996 | ddz = dtick(dz*max(0.5*xyr*diff(xlim)/yscale(end),1)); 997 | ztick = (ddz*ceil(zscale(1)/ddz)):ddz:zscale(end); 998 | rgbscale = ind2rgb(uint16(min(max((zscale - zmin)/dz,0),1)*(size(cmap,1)-1)),cmap); 999 | 1000 | ysc = ylim(1); 1001 | hold on 1002 | imagesc(xsc + wsc*[-1,1]/2,ysc + yscale,repmat(rgbscale,1,2),'clipping','off'); 1003 | patch(xsc + wsc*[-1,1,1,-1],ysc + yscale(end)*[0,0,1,1],'k','FaceColor','none','Clipping','off') 1004 | text(xsc + 2*wsc + zeros(size(ztick)),ysc + (ztick - zscale(1))*0.5*diff(ylim)/diff(zscale([1,end])),num2str(ztick'), ... 1005 | 'HorizontalAlignment','left','VerticalAlignment','middle','FontSize',fs*.75) 1006 | % indicates min and max Z values 1007 | text(xsc,ysc - bwy/2,sprintf('%g %s',roundsd(zlim(1),3),zunit),'FontWeight','bold', ... 1008 | 'HorizontalAlignment','left','VerticalAlignment','top','FontSize',fs*.75) 1009 | text(xsc,ysc + .5*diff(ylim) + bwy/2,sprintf('%g %s',roundsd(zlim(2),3),zunit),'FontWeight','bold', ... 1010 | 'HorizontalAlignment','left','VerticalAlignment','bottom','FontSize',fs*.75) 1011 | 1012 | % frees axes only if not hold on 1013 | if ~holdon 1014 | hold off 1015 | end 1016 | 1017 | end 1018 | 1019 | if scale || kmscale 1020 | % -- distance scale (in km) 1021 | if dms 1022 | fsc = degkm; 1023 | else 1024 | fsc = zratio/1e3; 1025 | end 1026 | dkm = dtick(diff(ylim)*fsc); 1027 | ysc = ylim(2) - 0.5*dkm/fsc; 1028 | if dkm > 1 1029 | skm = sprintf('%g km',dkm); 1030 | else 1031 | skm = sprintf('%g m',dkm*1000); 1032 | end 1033 | if kmscale 1034 | xsc = xlim(1) + wsc*2; 1035 | ysc = ylim(1) + wsc*2; 1036 | patch(xsc + dkm*[0,1,1,0]/fsc,ysc + wsc*[-1,-1,0,0],'k','FaceColor','w') 1037 | for n = 0:2:(dkm-1) 1038 | patch(xsc + (n + [0,1,1,0])/fsc,ysc + wsc*[-1,-1,0,0],'k','FaceColor','k') 1039 | end 1040 | text(xsc + .5*dkm/fsc,ysc,skm,'HorizontalAlignment','center','VerticalAlignment','bottom', ... 1041 | 'Color','k','FontWeight','bold','FontSize',fs) 1042 | else 1043 | patch(xsc + wsc*[-1,-1,0,0],ysc + dkm*0.5*[-1,1,1,-1]/fsc,'k','FaceColor',grey,'clipping','off') 1044 | text(xsc,ysc,skm,'rotation',-90,'HorizontalAlignment','center','VerticalAlignment','bottom', ... 1045 | 'Color',grey,'FontWeight','bold','FontSize',fs*.75) 1046 | end 1047 | end 1048 | 1049 | 1050 | if nargout > 0 1051 | varargout{1} = hh; 1052 | end 1053 | if nargout > 1 1054 | varargout{2} = I; 1055 | end 1056 | if nargout > 2 1057 | varargout{3} = z; 1058 | end 1059 | 1060 | 1061 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1062 | function shadow=hillshade(z,az,el,dx,dy,method) 1063 | %HILLSHADE Illumination of a surface 1064 | % SHADOW=HILLSHADE(Z,AZ,EL) computes the hypothetical illumination of a 1065 | % surface given by a grid Z, using light azimuth AZ (in degree from North) and 1066 | % light elevation (altitude) EL (in degree from horizon). 1067 | 1068 | % zenith angle 1069 | if strcmpi(method,'light') 1070 | zenith = pi/2; 1071 | else 1072 | zenith = (90 - el)*pi/180; 1073 | end 1074 | 1075 | % azimuth angle 1076 | azimuth = (360 - az + 90)*pi/180; 1077 | 1078 | fx = conv2(z,[-1,0,1;-2,0,2;-1,0,1]/8/dx,'same'); 1079 | fy = conv2(z,[-1,-2,-1;0,0,0;1,2,1]/8/dy,'same'); 1080 | slope = atan(sqrt(fx.^2 + fy.^2)); 1081 | aspect = atan2(fy,fx); 1082 | %k0 = (aspect < 0); 1083 | %aspect(k0) = aspect(k0) + 2*pi; 1084 | %aspect(fx == 0 & fy > 0) = pi/2; 1085 | %aspect(fx == 0 & fy < 0) = 2*pi - pi/2; 1086 | 1087 | shadow = zeros([size(fx),numel(azimuth)]); 1088 | for n = 1:numel(azimuth) 1089 | shadow(:,:,n) = (cos(zenith).*cos(slope) + sin(zenith).*sin(slope).*cos(azimuth(n) - aspect)); 1090 | end 1091 | if numel(azimuth) > 1 1092 | shadow = max(shadow,[],3); 1093 | end 1094 | 1095 | shadow(isnan(shadow)) = 0; 1096 | 1097 | 1098 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1099 | function y = nmedian(x,n) 1100 | %NMEDIAN Generalized median filter 1101 | % NMEDIAN(X,N) sorts elemets of X and returns N-th value (N normalized). 1102 | % So: 1103 | % N = 0 is minimum value 1104 | % N = 0.5 is median value 1105 | % N = 1 is maximum value 1106 | 1107 | if nargin < 2 1108 | n = 0.5; 1109 | end 1110 | y = interp1(sort(x(:)),n*(numel(x)-1) + 1); 1111 | 1112 | 1113 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1114 | function dd = dtick(dlim,deg) 1115 | %DTICK Tick intervals 1116 | 1117 | if nargin < 2 1118 | deg = 0; 1119 | end 1120 | 1121 | if deg && dlim <= 2/60 1122 | % less than 2 minutes: base 36 1123 | m = 10^floor(log10(dlim*36))/36; 1124 | elseif deg && dlim <= 2 1125 | % less than 2 degrees: base 6 1126 | m = 10^floor(log10(dlim*6))/6; 1127 | else 1128 | % more than few degrees or not degrees: decimal rules 1129 | m = 10^floor(log10(dlim)); 1130 | end 1131 | p = ceil(dlim/m); 1132 | if p <= 1 1133 | dd = .1*m; 1134 | elseif p == 2 1135 | dd = .2*m; 1136 | elseif p <= 5 1137 | dd = .5*m; 1138 | else 1139 | dd = m; 1140 | end 1141 | 1142 | 1143 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1144 | function s = deg2dms(x,ll,dec,fs) 1145 | %DEG2DMS Degree/minute/second display 1146 | 1147 | % smaller font for decimals 1148 | fs2 = sprintf('\\fontsize{%d}',round(fs/1.25)); 1149 | 1150 | if dec 1151 | ss = split(sprintf('%1.7g',x),'.'); 1152 | s = fliplr(regexprep(fliplr(ss{1}),'(\d{3})','$1 ')); 1153 | if length(ss) > 1 1154 | s = sprintf('%s.{%s%s}',s,fs2,ss{2}); 1155 | end 1156 | else 1157 | xa = abs(x) + 1/360000; 1158 | sd = sprintf('%d%c',floor(xa),176); % ASCII char 176 is the degree sign 1159 | sm = ''; 1160 | ss = ''; 1161 | if mod(x,1) 1162 | sm = sprintf('%02d''',floor(mod(60*xa,60))); 1163 | sa = floor(mod(3600*xa,60)); 1164 | if sa 1165 | ss = sprintf('%02d"',sa); 1166 | else 1167 | if strcmp(sm,'00''') 1168 | sm = ''; 1169 | end 1170 | end 1171 | end 1172 | s = sd; 1173 | if ~isempty(sm) || ~isempty(ss) 1174 | s = [s,'{',fs2,sm,ss,'}']; 1175 | end 1176 | s = [s,ll{1+int8(x<0)}]; 1177 | end 1178 | 1179 | 1180 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1181 | function y = watermark(x,n) 1182 | % makes colormap x watermark of ratio n 1183 | if nargin < 2 1184 | n = 2; 1185 | end 1186 | 1187 | if n == 0 1188 | y = x; 1189 | else 1190 | y = (x/n + 1 - 1/n); 1191 | end 1192 | 1193 | 1194 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1195 | function y=saturation(x,n) 1196 | % changes the color saturation by ratio n 1197 | 1198 | % first needs to convert from RGB to HSV 1199 | if ndims(x) == 3 1200 | r = x(:,:,1); 1201 | g = x(:,:,2); 1202 | b = x(:,:,3); 1203 | mx = max(x,[],3); % max(r,g,b) 1204 | mn = min(x,[],3); % min(r,g,b) 1205 | else 1206 | r = x(:,1); 1207 | g = x(:,2); 1208 | b = x(:,3); 1209 | mx = max(x,[],2); % max(r,g,b) 1210 | mn = min(x,[],2); % min(r,g,b) 1211 | end 1212 | 1213 | s = zeros(size(r)); 1214 | h = zeros(size(r)); 1215 | dt = mx - mn; 1216 | v = mx; 1217 | 1218 | k = mx>0 & dt>0; 1219 | if any(k(:)) 1220 | s(k) = dt(k)./mx(k); 1221 | kr = (k & mx==r); 1222 | kg = (k & mx==g); 1223 | kb = (k & mx==b); 1224 | h(kr) = mod(60*(g(kr) - b(kr))./dt(kr),360); 1225 | h(kg) = 60*(b(kg) - r(kg))./dt(kg) + 120; 1226 | h(kb) = 60*(r(kb) - g(kb))./dt(kb) + 240; 1227 | end 1228 | 1229 | % changes the saturation channel 1230 | s = s*n; 1231 | s(s>1) = 1; 1232 | 1233 | % converting back from HSV to RGB 1234 | hi = mod(floor(h/60),6); 1235 | f = h/60 - hi; 1236 | l = v.*(1 - s); 1237 | m = v.*(1 - f.*s); 1238 | n = v.*(1 - (1 - f).*s); 1239 | 1240 | % default for hi == 0 1241 | r = v; g = n; b = l; 1242 | 1243 | k = hi==1; 1244 | r(k) = m(k); g(k) = v(k); b(k) = l(k); 1245 | k = hi==2; 1246 | r(k) = l(k); g(k) = v(k); b(k) =n(k); 1247 | k = hi==3; 1248 | r(k) = l(k); g(k) = m(k); b(k) = v(k); 1249 | k = hi==4; 1250 | r(k) = n(k); g(k) = l(k); b(k) = v(k); 1251 | k = hi==5; 1252 | r(k) = v(k); g(k) = l(k); b(k) = m(k); 1253 | 1254 | y = cat(ndims(x),r,g,b); 1255 | 1256 | 1257 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1258 | function y=rgb2gray(x) 1259 | % removes color information 1260 | 1261 | if ndims(x) == 3 1262 | y = repmat(0.2989*x(:,:,1) + 0.5870*x(:,:,2) + 0.1140*x(:,:,3),[1,1,3]); 1263 | else 1264 | y = repmat(0.2989*x(:,1) + 0.5870*x(:,2) + 0.1140*x(:,3),[1,3]); 1265 | end 1266 | 1267 | 1268 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1269 | function z = fillgap(x,y,z) 1270 | % This function reproduces the core of NANINTERP2 1271 | 1272 | sz = size(z); 1273 | k = find(isnan(z)); 1274 | k(k == 1 | k == numel(z)) = []; % removes first and last index (if exist) 1275 | if ~isempty(k) 1276 | [xx,yy] = meshgrid(x,y); 1277 | mask = false(sz); 1278 | k2 = ind90(sz,k); % k2 is linear index in the row order 1279 | % sets to 1 every previous and next index, both in column and row order 1280 | mask([k-1;k+1;ind90(fliplr(sz),[k2-1;k2+1])]) = true; 1281 | mask(k) = false; % removes the novalue index 1282 | z(k) = griddata(xx(mask),yy(mask),z(mask),xx(k),yy(k)); 1283 | end 1284 | 1285 | 1286 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1287 | function k2 = ind90(sz,k) 1288 | 1289 | [i,j] = ind2sub(sz,k); 1290 | k2 = sub2ind(fliplr(sz),j,i); % switched i and j: k2 is linear index in row order 1291 | 1292 | 1293 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1294 | function k = islake(z) 1295 | % ISLAKE mask of zero gradient on 3x3 tiles 1296 | % We use diff matrix in row and column directions, and shift it to build 1297 | % a single vectorized test of surrounding pixels. To do this we must 1298 | % concatenate unit vectors in different combinations... 1299 | 1300 | dx = diff(z,1,2); % differences in X direction 1301 | dy = diff(z,1,1); % differences in Y direction 1302 | u1 = ones(size(z,1),1); % row unit vector 1303 | u2 = ones(1,size(z,2)); % column unit vector 1304 | u2r = u2(2:end); 1305 | 1306 | % index of the tiles center pixel 1307 | k = ( ... 1308 | [u2;dy] == 0 & [dy;u2] == 0 & ... 1309 | [u1,dx] == 0 & [dx,u1] == 0 & ... 1310 | [u1,[dx(2:end,:);u2r]] == 0 & [[dx(2:end,:);u2r],u1] == 0 & ... 1311 | [u1,[u2r;dx(1:end-1,:)]] == 0 & [[u2r;dx(1:end-1,:)],u1] == 0 ... 1312 | ); 1313 | 1314 | % now extends it to surrounding pixels 1315 | k(1:end-1,:) = (k(1:end-1,:) | k(2:end,:)); 1316 | k(2:end,:) = (k(2:end,:) | k(1:end-1,:)); 1317 | k(:,1:end-1) = (k(:,1:end-1) | k(:,2:end)); 1318 | k(:,2:end) = (k(:,2:end) | k(:,1:end-1)); 1319 | 1320 | 1321 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1322 | function s = isrgb(x,n) 1323 | 1324 | if nargin < 2 1325 | n = 0; 1326 | end 1327 | if isnumeric(x) && (n == 1 && all(size(x) == [1,3]) || n == 0 && size(x,2) == 3) ... 1328 | && all(x(:) >= 0 & x(:) <= 1) 1329 | s = 1; 1330 | else 1331 | s = 0; 1332 | end 1333 | 1334 | 1335 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1336 | function s = isperc(x) 1337 | 1338 | if isnumeric(x) && isscalar(x) && x >= 0 && x <= 100 1339 | s = 1; 1340 | else 1341 | s = 0; 1342 | end 1343 | 1344 | 1345 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1346 | function s = isvec(x,n) 1347 | 1348 | if nargin < 2 1349 | n = 2; 1350 | end 1351 | if isnumeric(x) && (nargin > 1 && any(numel(x) == n) || numel(x) > 0) 1352 | s = 1; 1353 | else 1354 | s = 0; 1355 | end 1356 | 1357 | 1358 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1359 | function y=roundsd(x,n) 1360 | 1361 | og = 10.^(floor(log10(abs(x)) - n + 1)); 1362 | y = round(x./og).*og; 1363 | y(x==0) = 0; 1364 | 1365 | 1366 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1367 | function [s,v] = checkparam(arg,nam,func,val) 1368 | 1369 | switch func2str(func) 1370 | case 'isscalar' 1371 | num = 1; 1372 | mes = 'scalar value'; 1373 | case 'isperc' 1374 | num = 1; 1375 | mes = 'percentage scalar value'; 1376 | case 'isvec' 1377 | num = 1; 1378 | if nargin < 4 1379 | val = 2; 1380 | end 1381 | mes = sprintf('%d-element vector',val); 1382 | case 'isrgb' 1383 | num = 1; 1384 | mes = '[R,G,B] vector with 0.0 to 1.0 values'; 1385 | case 'ischar' 1386 | num = 0; 1387 | mes = 'string'; 1388 | if nargin > 3 1389 | mes = sprintf('%s (%s)',mes,strjoin(val,' or ')); 1390 | end 1391 | otherwise 1392 | num = 1; 1393 | mes = 'value'; 1394 | end 1395 | 1396 | s = 0; 1397 | v = []; 1398 | k = find(strcmpi(arg,nam),1,'last'); 1399 | if ~isempty(k) 1400 | if (k + 1) <= length(arg) ... 1401 | && (~num || isnumeric(arg{k+1})) ... 1402 | && (nargin < 4 && func(arg{k+1}) ... 1403 | || (nargin > 3 && (strcmp(func2str(func),'ischar') && ismember(arg{k+1},val)) ... 1404 | || strcmp(func2str(func),'isvec') && func(arg{k+1},val))) 1405 | v = arg{k+1}; 1406 | s = 1; 1407 | else 1408 | error('%s option must be followed by a valid %s.',upper(nam),mes) 1409 | end 1410 | end 1411 | 1412 | 1413 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1414 | function s=strjoin(c,d) 1415 | %STRJOIN Join cell array of strings 1416 | %(this is for Matlab versions < 2013a backward compatibility) 1417 | 1418 | if nargin < 2 1419 | d = ''; 1420 | end 1421 | n = numel(c); 1422 | ss = cell(2,n); 1423 | ss(1,:) = reshape(c,1,n); 1424 | ss(2,1:n-1) = {d}; 1425 | ss{end} = ''; 1426 | s = [ss{:}]; 1427 | -------------------------------------------------------------------------------- /dem/dem_example_dome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/dem/dem_example_dome.png -------------------------------------------------------------------------------- /dem/dem_example_guadeloupe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/dem/dem_example_guadeloupe.png -------------------------------------------------------------------------------- /dem/dem_example_indonesia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/dem/dem_example_indonesia.png -------------------------------------------------------------------------------- /dem/dem_example_moon_bone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/dem/dem_example_moon_bone.png -------------------------------------------------------------------------------- /dem/landcolor.m: -------------------------------------------------------------------------------- 1 | function y = landcolor(n) 2 | %LANDCOLOR Land colormap 3 | % 4 | % Author: Francois Beauducel 5 | % $Revision: 1.0.0 $ $Date: 2012/05/17 11:22:44 $ 6 | 7 | J = [ ... 8 | 0.095678 0.53427 0.21682 9 | 0.15785 0.5979 0.23274 10 | 0.21286 0.64673 0.2514 11 | 0.26411 0.68789 0.27268 12 | 0.32959 0.72416 0.31308 13 | 0.39794 0.75695 0.36038 14 | 0.46153 0.7871 0.40624 15 | 0.52108 0.81516 0.45135 16 | 0.57702 0.84152 0.49547 17 | 0.62973 0.86645 0.53891 18 | 0.67946 0.89016 0.58187 19 | 0.72647 0.91282 0.62427 20 | 0.77095 0.93455 0.66619 21 | 0.81306 0.95546 0.70772 22 | 0.85292 0.97563 0.7489 23 | 0.89066 0.99514 0.78976 24 | 0.88379 0.98595 0.77038 25 | 0.86389 0.96758 0.73236 26 | 0.84615 0.94972 0.69623 27 | 0.8303 0.93233 0.66186 28 | 0.81612 0.91536 0.6291 29 | 0.80341 0.8988 0.59784 30 | 0.79201 0.8826 0.56795 31 | 0.78191 0.86676 0.53946 32 | 0.7729 0.85123 0.51224 33 | 0.76479 0.83602 0.48615 34 | 0.75747 0.8211 0.46111 35 | 0.75084 0.80645 0.43704 36 | 0.74506 0.79206 0.41414 37 | 0.73981 0.77792 0.39211 38 | 0.73501 0.76401 0.37089 39 | 0.73068 0.75033 0.35052 40 | 0.72683 0.73685 0.33106 41 | 0.72042 0.72074 0.31228 42 | 0.71032 0.70085 0.29417 43 | 0.69761 0.67821 0.27694 44 | 0.68489 0.65558 0.26026 45 | 0.67235 0.63313 0.24418 46 | 0.65997 0.61082 0.22889 47 | 0.64775 0.58874 0.21406 48 | 0.63568 0.56689 0.19983 49 | 0.62376 0.54527 0.18622 50 | 0.61197 0.52391 0.17299 51 | 0.60033 0.50283 0.16046 52 | 0.58881 0.48203 0.14832 53 | 0.57742 0.46151 0.13667 54 | 0.56616 0.44133 0.12555 55 | 0.55502 0.4214 0.11472 56 | 0.54398 0.4019 0.10456 57 | 0.53306 0.38266 0.094633 58 | 0.52226 0.36382 0.085242 59 | 0.51155 0.3453 0.076179 60 | 0.50095 0.32714 0.067515 61 | 0.49045 0.30938 0.059259 62 | 0.48005 0.29193 0.051294 63 | 0.46973 0.27495 0.043796 64 | 0.45951 0.25823 0.0365 65 | 0.44938 0.24206 0.029715 66 | 0.43934 0.22609 0.023063 67 | 0.42938 0.21074 0.016949 68 | 0.41951 0.19556 0.010917 69 | 0.40971 0.18105 0.0054326 70 | 0.4 0.16667 0 71 | ]; 72 | 73 | l = length(J); 74 | if nargin < 1 75 | n = 256; 76 | end 77 | y = interp1(1:l,J,linspace(1,l,n),'*linear'); 78 | 79 | -------------------------------------------------------------------------------- /dem/seacolor.m: -------------------------------------------------------------------------------- 1 | function y = seacolor(n) 2 | %SEACOLOR Sea colormap adapted from NGDC ETOPO1 3 | % 4 | % Author: Francois Beauducel 5 | 6 | J = [ ... 7 | 0.0392 0 0.4745 8 | 0.1020 0 0.5373 9 | 0.1020 0 0.5373 10 | 0.1490 0 0.5961 11 | 0.1490 0 0.5961 12 | 0.1059 0.0118 0.6510 13 | 0.1059 0.0118 0.6510 14 | 0.0627 0.0235 0.7059 15 | 0.0627 0.0235 0.7059 16 | 0.0196 0.0353 0.7569 17 | 0.0196 0.0353 0.7569 18 | 0 0.0549 0.7961 19 | 0 0.0549 0.7961 20 | 0 0.0863 0.8235 21 | 0 0.0863 0.8235 22 | 0 0.1176 0.8471 23 | 0 0.1176 0.8471 24 | 0 0.1529 0.8745 25 | 0 0.1529 0.8745 26 | 0.0471 0.2667 0.9059 27 | 0.0471 0.2667 0.9059 28 | 0.1020 0.4000 0.9412 29 | 0.1020 0.4000 0.9412 30 | 0.0745 0.4588 0.9569 31 | 0.0745 0.4588 0.9569 32 | 0.0549 0.5216 0.9765 33 | 0.0549 0.5216 0.9765 34 | 0.0824 0.6196 0.9882 35 | 0.0824 0.6196 0.9882 36 | 0.1176 0.6980 1.0000 37 | 0.1176 0.6980 1.0000 38 | 0.1686 0.7294 1.0000 39 | 0.1686 0.7294 1.0000 40 | 0.2157 0.7569 1.0000 41 | 0.2157 0.7569 1.0000 42 | 0.2549 0.7843 1.0000 43 | 0.2549 0.7843 1.0000 44 | 0.3098 0.8235 1.0000 45 | 0.3098 0.8235 1.0000 46 | 0.3686 0.8745 1.0000 47 | 0.3686 0.8745 1.0000 48 | 0.5412 0.8902 1.0000 49 | 0.5412 0.8902 1.0000 50 | 0.7373 0.9020 1.0000 51 | ]; 52 | 53 | l = length(J); 54 | if nargin < 1 55 | n = 256; 56 | end 57 | y = interp1(1:l,J,linspace(1,l,n),'*linear'); 58 | 59 | -------------------------------------------------------------------------------- /grd/egrd.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/grd/egrd.m -------------------------------------------------------------------------------- /grd/igrd.m: -------------------------------------------------------------------------------- 1 | function varargout = igrd(fn,crop) 2 | %IGRD Import DEM in .GRD formats (Surfer, ArcInfo / GMT). 3 | % [X,Y,Z]=IGRD(FILENAME) returns the Digital Elevation Model data 4 | % defined by X and Y (vectors or matrix) and matrix Z of elevations. 5 | % FILENAME is a file using one the data grid .GRD formats: 6 | % - Golden Sofware Surfer (ASCII or binary), 7 | % - Arc/Info (ASCII), 8 | % - Generic Mapping Tool (ASCII only). 9 | % 10 | % IGRD(FILENAME,[X1 X2 Y1 Y2]) will crop the grid using area limits 11 | % [X1,X2] for X, and [Y1,Y2] for Y. 12 | % 13 | % G=IGRD(...) returns a structure G with fields: 14 | % x: X vector 15 | % y: Y vector 16 | % z: Z matrix 17 | % 18 | % NoData values are replaced by NaN. 19 | % 20 | % Author: François Beauducel, IPG Paris. 21 | % Created: 1996 22 | % Updated: 2022-05-23 23 | % 24 | % References: 25 | % Golden Software Surfer, http://www.goldensoftware.com/ 26 | % GMT (Generic Mapping Tools), http://gmt.soest.hawaii.edu 27 | 28 | % Copyright (c) 1996-2022, François Beauducel, covered by BSD License. 29 | % All rights reserved. 30 | % 31 | % Redistribution and use in source and binary forms, with or without 32 | % modification, are permitted provided that the following conditions are 33 | % met: 34 | % 35 | % * Redistributions of source code must retain the above copyright 36 | % notice, this list of conditions and the following disclaimer. 37 | % * Redistributions in binary form must reproduce the above copyright 38 | % notice, this list of conditions and the following disclaimer in 39 | % the documentation and/or other materials provided with the distribution 40 | % 41 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 42 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 43 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 44 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 45 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 46 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 47 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 48 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 49 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 50 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 51 | % POSSIBILITY OF SUCH DAMAGE. 52 | 53 | % lauch Open File dialog box if no file is specified 54 | if nargin < 1 55 | [f,p] = uigetfile('*.grd','Select the GRD file'); 56 | fn = [p,f]; 57 | end 58 | 59 | ndv = -99999; % default NoValue 60 | 61 | if ~exist(fn,'file') 62 | error('File "%s" not found.',fn); 63 | end 64 | 65 | fprintf('IGRD: importing "%s"...\n',fn); 66 | 67 | fid = fopen(fn, 'r'); 68 | line = fgets(fid); % reads 1st line header 69 | 70 | % case of Golden Software/Surfer grid ASCII file 71 | if regexpi(line,'^DSAA') 72 | sz = fscanf(fid, '%d', [1 2]); 73 | xm = fscanf(fid, '%f', 2); 74 | ym = fscanf(fid, '%f', 2); 75 | zm = fscanf(fid, '%f', 2); 76 | fprintf('--> found GS/Surfer ascii grid: %dx%d, xlim = [%g %g], ylim = [%g %g], zlim = [%g %g] ...',sz,xm,ym,zm); 77 | z = fscanf(fid, '%f', sz)'; 78 | fprintf(' done.\n'); 79 | 80 | % case of Golden Software/Surfer grid BINARY file 81 | elseif regexpi(line,'^DSBB') 82 | fclose(fid); 83 | fid = fopen(fn,'rb'); 84 | co = fscanf(fid,'%c',4); 85 | sz = fread(fid,2,'int16')'; 86 | xm = fread(fid,2,'float64')'; 87 | ym = fread(fid,2,'float64')'; 88 | zm = fread(fid,2,'float64')'; 89 | fprintf('--> found GS/Surfer binary grid: %dx%d, xlim = [%g %g], ylim = [%g %g], zlim = [%g %g] ...',sz,xm,ym,zm); 90 | z = reshape(fread(fid,'float32'),sz)'; 91 | fprintf(' done.\n'); 92 | 93 | % case of ESRI/ArcInfo grid ASCII file (added in 2009) 94 | elseif regexpi(line,'^ncols') 95 | s = textscan(line,'%s'); 96 | 97 | % the two formats exist: 'ncols xx' or 'xx ncols'... 98 | if strcmpi(s{1}(1),'ncols') 99 | x = 1; 100 | v = 2; 101 | cf = '%s%n'; 102 | else 103 | x = 2; 104 | v = 1; 105 | cf = '%n%s'; 106 | end 107 | sz(1) = str2double(s{1}{v}); 108 | 109 | for i = 1:6 110 | line = fgets(fid); 111 | s = textscan(line,cf); 112 | if isscalar(s{v}) 113 | switch lower(s{x}{:}) 114 | case 'nrows' 115 | sz(2) = s{v}; 116 | case {'xllcenter','xll'} 117 | xm(1) = s{v}; 118 | dx2 = 0; 119 | case 'xllcorner' 120 | xm(1) = s{v}; 121 | dx2 = 1; 122 | case {'yllcenter','yll'} 123 | ym(1) = s{v}; 124 | dy2 = 0; 125 | case 'yllcorner' 126 | ym(1) = s{v}; 127 | dy2 = 1; 128 | case 'cellsize' 129 | csx = s{v}; 130 | csy = s{v}; 131 | case 'dx' 132 | csx = s{v}; 133 | case 'dy' 134 | csy = s{v}; 135 | case 'nodata_value' 136 | ndv = s{v}; 137 | break 138 | otherwise 139 | warning('"%s" (=%g) header unknown.\n',s{x}{:},s{v}) 140 | end 141 | else 142 | % it happens that files have only 5 lines of header (instead of 6) 143 | fseek(fid,-length(line),'cof'); 144 | end 145 | end 146 | xm(2) = xm(1) + (sz(1)-1)*csx; 147 | ym(2) = ym(1) + (sz(2)-1)*csy; % note the reverse order of Y-axis vector 148 | % applies half cell size if corner origin 149 | xm = xm + dx2*csx/2; 150 | ym = ym - dy2*csy/2; 151 | fprintf('--> found ESRI/ArcInfo ascii grid: %dx%d, xlim = [%g %g], ylim = [%g %g] ...',sz,xm,ym); 152 | z = flipud(fscanf(fid, '%f', sz)'); % transform needed because Z values are sorted rowwise 153 | fprintf(' done.\n'); 154 | 155 | elseif regexpi(line,'^CDF') 156 | error('"%s" is a binary netCDF grid file. Cannot be read, yet...\nConvert it first using "gmt grd2xyz -Ef" command.',fn) 157 | else 158 | error('"%s" is not a valid GRD file for this function.',fn) 159 | end 160 | 161 | fclose(fid); 162 | 163 | x = linspace(xm(1),xm(2),sz(1)); 164 | y = linspace(ym(1),ym(2),sz(2))'; 165 | 166 | % crop 167 | if nargin > 1 168 | kx = (x >= crop(1) & x <= crop(2)); 169 | ky = (y >= crop(3) & y <= crop(4)); 170 | x = x(kx); 171 | y = y(ky); 172 | z = z(ky,kx); 173 | end 174 | 175 | % replace NoData values by NaN 176 | z(z == ndv | abs(z) > 1e38) = NaN; 177 | 178 | if nargout == 3 179 | varargout{1} = x; 180 | varargout{2} = y; 181 | varargout{3} = z; 182 | else 183 | varargout{1} = struct('x',x,'y',y,'z',z); 184 | end 185 | -------------------------------------------------------------------------------- /greatcircle/README.md: -------------------------------------------------------------------------------- 1 | # Shortest and rhumb line path, distance and bearing 2 | 3 | ## greatcircle.m 4 | The function GREATCIRCLE computes the shortest path along the great circle ("as the crow flies") between two points defined by their geographic coordinates (latitude and longitude). With one output argument it returns distance or vector of distances, with two or more output arguments it returns path coordinates and optional vector of distances and bearing angles along the path. 5 | 6 | Example: 7 | ```matlab 8 | load topo 9 | contour(0:359,-89:90,topo,[0,0],'k') 10 | [lat,lon,dis] = greatcircle(48.8,2.3,35.7,139.7); 11 | hold on, plot(lon,lat,'r','linewidth',2), hold off 12 | title(sprintf('Paris to Tokyo = %g km',dis(end))) 13 | ``` 14 | 15 | ## loxodrome.m 16 | The function LOXODROME computes the path with a constant bearing, crossing all meridians of longitude at the same angle. It returns also a vector of distances and the bearing angle. 17 | 18 | Example: 19 | ```matlab 20 | load topo 21 | contour(0:359,-89:90,topo,[0,0],'k') 22 | [lat,lon,dis,bear] = loxodrome(48.8,2.3,35.7,139.7); 23 | hold on, plot(lon,lat,'r','linewidth',2), hold off 24 | title(sprintf('Paris to Tokyo = %g km - bear = %g N',dis(end),bear)) 25 | ``` 26 | 27 | Loxodrome path (also known as "rhumb line") is longer than great circle one, but still used in navigation as it is easier to follow with a compass. 28 | 29 | ![greatcircle example](greatcircle_example.png) 30 | 31 | Type 'doc greatcircle' or 'doc loxodrome' for syntax, help and examples. 32 | -------------------------------------------------------------------------------- /greatcircle/greatcircle.m: -------------------------------------------------------------------------------- 1 | function varargout=greatcircle(varargin) 2 | %GREATCIRCLE "As the crow flies" path, distance and bearing. 3 | % 4 | % GREATCIRCLE(LAT1,LON1,LAT2,LON2) returns the shortest distance (in km) 5 | % along the great circle between two points defined by spherical 6 | % coordinates latitude and longitude (in decimal degrees). If input 7 | % arguments are vectors or matrix, it returns a vector/matrix of 8 | % distances of the same size. 9 | % 10 | % [LAT,LON,DISTANCE,BEARING]=GREATCIRCLE(LAT1,LON1,LAT2,LON2) where all 11 | % input arguments are scalars, computes the way points of coordinates LAT 12 | % and LON, the distances (in km), and the bearing angles (in degrees from 13 | % North) along the path, with default 100 intermediate points. Note that 14 | % last bearing angle is NaN. 15 | % 16 | % [...]=GREATCIRCLE(...,N) uses N intermediate points. 17 | % 18 | % Example: 19 | % 20 | % load topo 21 | % contour(0:359,-89:90,topo,[0,0],'k') 22 | % [lat,lon,dis] = greatcircle(48.8,2.3,35.7,139.7); 23 | % hold on, plot(lon,lat,'r','linewidth',2), hold off 24 | % title(sprintf('Paris to Tokyo = %g km',dis(end))) 25 | % 26 | % See also LOXODROME. 27 | % 28 | % Author: Francois Beauducel 29 | % Created: 2012-07-26 30 | % Updated: 2019-05-28 31 | % 32 | % References: 33 | % http://www.movable-type.co.uk/scripts/latlong.html 34 | % http://en.wikipedia.org/wiki/Haversine_formula 35 | 36 | % Copyright (c) 2019, François Beauducel, covered by BSD License. 37 | % All rights reserved. 38 | % 39 | % Revision history: 40 | % 41 | % v1.4 [2019-05-28] 42 | % - greatcircle: fixes the singularity when LON1 and LON2 have 180° 43 | % of difference (thanks to Patrick Hew's comment) 44 | % 45 | % v1.3 [2012-12-24] 46 | % - greatcircle: two possible syntaxes (output distance or path way) 47 | % - loxodrome: bug correction 48 | % 49 | % v1.2 [2012-11-08] 50 | % - bug correction with bearing angle and distances. 51 | % 52 | % v1.1 [2012-10-30] 53 | % - adds loxodrome function 54 | % - improves input argument checking 55 | % 56 | % v1.0 [2012-07-26] 57 | % - initial commit 58 | % 59 | % Redistribution and use in source and binary forms, with or without 60 | % modification, are permitted provided that the following conditions are 61 | % met: 62 | % 63 | % * Redistributions of source code must retain the above copyright 64 | % notice, this list of conditions and the following disclaimer. 65 | % * Redistributions in binary form must reproduce the above copyright 66 | % notice, this list of conditions and the following disclaimer in 67 | % the documentation and/or other materials provided with the distribution 68 | % 69 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 70 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 71 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 72 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 73 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 74 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 75 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 76 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 77 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 78 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 79 | % POSSIBILITY OF SUCH DAMAGE. 80 | 81 | if nargin < 4 || nargin > 5 82 | error('Number of input arguments not correct.') 83 | else 84 | lat1 = varargin{1}; 85 | lon1 = varargin{2}; 86 | lat2 = varargin{3}; 87 | lon2 = varargin{4}; 88 | end 89 | 90 | if nargin < 5 91 | n = 100; 92 | else 93 | n = varargin{5}; 94 | end 95 | 96 | %if any(~cellfun(@isnumeric,varargin)) 97 | % error('All input arguments must be numeric.') 98 | %end 99 | 100 | rearth = 6371; % volumetric Earth radius (in km) 101 | 102 | if nargout > 1 103 | 104 | %if any(~cellfun(@isscalar,varargin)) 105 | % error('To compute path way all input arguments must be scalars.') 106 | %end 107 | 108 | % use shortest segment 109 | if abs(lon2 - lon1) > 180 110 | lon2 = lon2 + 360*sign(lon1 - lon2); 111 | end 112 | 113 | % special case of 180° difference between lon1 and lon2 (to avoid singularity 0/0) 114 | if abs(lon2 - lon1) == 180 115 | if lat2 > -lat1 116 | % through the North pole with temporary lat>90 117 | lat = linspace(lat1,180 - lat2,n); 118 | lon = lon1*ones(size(lat)); 119 | lon(lat>90) = lon2; 120 | lat(lat>90) = 180 - lat(lat>90); 121 | else 122 | % through the South pole with temporary lat<-90 123 | lat = linspace(lat1,-180 - lat2,n); 124 | lon = lon1*ones(size(lat)); 125 | lon(lat<-90) = lon2; 126 | lat(lat<-90) = -180 - lat(lat<-90); 127 | end 128 | else 129 | lon = linspace(lon1,lon2,n); 130 | % an other special case: simple segment at constant longitude 131 | if lon1 == lon2 132 | lat = linspace(lat1,lat2,n); 133 | else 134 | lat = atand((sind(lat1)*cosd(lat2)*sind(lon - lon2) - sind(lat2)*cosd(lat1)*sind(lon - lon1))./(cosd(lat1)*cosd(lat2).*sind(lon1 - lon2))); 135 | end 136 | end 137 | varargout = {lat,lon}; 138 | 139 | if nargout > 2 140 | % computes the distance using Haversine's formula 141 | a = haversin(lat - lat1) + cosd(lat1).*cosd(lat).*haversin(lon - lon1); 142 | dist = rearth*2*asin(sqrt(a)); 143 | varargout = {lat,lon,dist}; 144 | end 145 | 146 | if nargout > 3 147 | % computes the bearing angle 148 | bear = atan2(sind(lon2-lon).*cosd(lat2),cosd(lat).*sind(lat2) - sind(lat).*cosd(lat2).*cosd(lon2-lon))*180/pi; 149 | bear(end) = NaN; 150 | varargout = {lat,lon,dist,bear}; 151 | end 152 | 153 | else 154 | dist = rearth*2*asin(sqrt(haversin(lat2 - lat1) + cosd(lat1).*cosd(lat2).*haversin(lon2 - lon1))); 155 | varargout = {dist}; 156 | end 157 | 158 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 159 | function y = haversin(x) 160 | % Haversin's function 161 | 162 | y = sind(x/2).^2; 163 | 164 | -------------------------------------------------------------------------------- /greatcircle/greatcircle_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/greatcircle/greatcircle_example.png -------------------------------------------------------------------------------- /greatcircle/loxodrome.m: -------------------------------------------------------------------------------- 1 | function [lat,lon,dist,bear]=loxodrome(varargin) 2 | %LOXODROME Rhumb line path and distance. 3 | % 4 | % [LAT,LON]=LOXODROME(LAT1,LON1,LAT2,LON2) computes the line between two 5 | % points defined by their spherical coordinates latitude and longitude 6 | % (in decimal degrees), crossing all meridians of longitude at the same 7 | % angle, i.e. a path derived from an initial and constant bearing. 8 | % LAT and LON contain vectors of coordinates of the way points. 9 | % 10 | % [LAT,LON,DISTANCE,BEARING]=LOXODROME(...) returns also vector of 11 | % distances (in km) along the path way, and constant bearing angle (in 12 | % degrees from North, a scalar). 13 | % 14 | % [...]=LOXODROME(...,N) uses N intermediate points (default is 100). 15 | % 16 | % Example: 17 | % 18 | % load topo 19 | % contour(0:359,-89:90,topo,[0,0],'k') 20 | % [lat,lon,dis,bear] = loxodrome(48.8,2.3,35.7,139.7); 21 | % hold on, plot(lon,lat,'r','linewidth',2), hold off 22 | % title(sprintf('Paris to Tokyo = %g km - bear = %g N',dis(end),bear)) 23 | % 24 | % 25 | % See also GREATCIRCLE. 26 | % 27 | % Author: Francois Beauducel 28 | % Created: 2012-10-30 29 | % Updated: 2012-11-08 30 | % 31 | % References: 32 | % http://en.wikipedia.org/wiki/Rhumb_line 33 | % 34 | % Acknowledgments: Jorge David Taramona Perea, Jacques Vernin. 35 | 36 | % Development history: 37 | % [2012-11-08] correction of the bearing angle calculation, with 38 | % consequence to distance values. Detection by Jacques Vernin. 39 | % 40 | % Copyright (c) 2012, François Beauducel, covered by BSD License. 41 | % All rights reserved. 42 | % 43 | % Redistribution and use in source and binary forms, with or without 44 | % modification, are permitted provided that the following conditions are 45 | % met: 46 | % 47 | % * Redistributions of source code must retain the above copyright 48 | % notice, this list of conditions and the following disclaimer. 49 | % * Redistributions in binary form must reproduce the above copyright 50 | % notice, this list of conditions and the following disclaimer in 51 | % the documentation and/or other materials provided with the distribution 52 | % 53 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 54 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 55 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 56 | % ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 57 | % LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 58 | % CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 59 | % SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 60 | % INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 61 | % CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 62 | % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 63 | % POSSIBILITY OF SUCH DAMAGE. 64 | 65 | if nargin < 4 66 | error('Number of input arguments not correct.') 67 | else 68 | lat1 = varargin{1}; 69 | lon1 = varargin{2}; 70 | lat2 = varargin{3}; 71 | lon2 = varargin{4}; 72 | end 73 | 74 | if nargin < 5 75 | n = 100; 76 | else 77 | n = varargin{5}; 78 | end 79 | 80 | if any(~cellfun(@isnumeric,varargin)) | any(~cellfun(@isscalar,varargin)) 81 | error('All input arguments must be numeric scalars.') 82 | end 83 | 84 | % length of one degree of latitude (in km) 85 | degkm = 6371*pi/180; 86 | 87 | % defines linear vectors of latitudes and longitudes 88 | lat = arcgdd(linspace(gdd(lat1),gdd(lat2),n)); 89 | lon = linspace(lon1,lon2,n); 90 | 91 | if nargout > 2 92 | % computes the bearing angle 93 | bear = atan2d(lon2 - lon1,arcgdd(lat2) - arcgdd(lat1)); 94 | 95 | % computes the distance along the path 96 | if lat1 ~= lat2 97 | dist = degkm*(lat - lat1)./cosd(bear); 98 | else 99 | dist = degkm*(lon - lon1).*cosd(lat1); 100 | end 101 | end 102 | 103 | 104 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 105 | function y = gdd(x) 106 | %GDD Gudermann function in degree. 107 | 108 | y = atand(sinh(x*pi/180)); 109 | 110 | 111 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 112 | function y = arcgdd(x) 113 | %ARCGDD Gudermann inverse function in degree. 114 | 115 | %y = 180/pi * log(tand(45 + x/2)); 116 | y = atanh(sind(x))*180/pi; -------------------------------------------------------------------------------- /ibil/ibil.m: -------------------------------------------------------------------------------- 1 | function [x,y,z] = ibil(f,crop) 2 | %IBIL read ESRI BIL raster files 3 | % [X,Y,Z] = IBIL(F) imports files F.bin and F.hdr in coordinates vectors 4 | % X and Y, and elevation matrix Z. 5 | % 6 | % [X,Y,Z] = IBIL(F,[XMIN XMAX YMIN YMAX]) crops at X/Y limits. 7 | % 8 | % DEM = IBIL(...) returns DEM struct: 9 | % lat: latitude vector (Y) 10 | % lon: longitude vector (X) 11 | % z: elevation matrix (in meters) 12 | % 13 | % F must be a filename without extension, pointing to F.bin and F.hdr 14 | % 15 | % For example, get ETOPO1 data files at: 16 | % https://www.ngdc.noaa.gov/mgg/global/relief/ETOPO1/data/bedrock/grid_registered/binary/etopo1_bed_g_i2.zip 17 | % 18 | % Author: F. Beauducel, WEBOBS/IPGP 19 | % Created: 2013-09-14, Paris, France 20 | % Updated: 2016-12-22 21 | 22 | 23 | cropflag = 0; 24 | if nargin > 1 && numel(crop) == 4 25 | cropflag = 1; 26 | end 27 | 28 | fd = [f,'.bin']; 29 | fh = [f,'.hdr']; 30 | 31 | %fprintf('WEBOBS{ibil}: importing "%s" file... ',fd); 32 | 33 | if ~exist(fd,'file') 34 | error('Cannot find data file %s',fd) 35 | end 36 | if exist(fh,'file') 37 | fid = fopen(fh,'rt'); 38 | s = textscan(fid,'%s %s'); 39 | fclose(fid); 40 | else 41 | error('Cannot find header file %s',fh) 42 | end 43 | 44 | for h = 1:length(s{1}) 45 | eval(sprintf('X.%s=''%s'';',s{1}{h},upper(s{2}{h}))); 46 | end 47 | 48 | ncols = str2double(X.NCOLS); 49 | nrows = str2double(X.NROWS); 50 | xll = str2double(X.XLLCENTER); 51 | yll = str2double(X.YLLCENTER); 52 | cellsize = str2double(X.CELLSIZE); 53 | 54 | switch(X.BYTEORDER) 55 | case 'LSBFIRST' 56 | byteorder = 'l'; 57 | otherwise 58 | byteorder = 'b'; 59 | end 60 | 61 | switch(X.NUMBERTYPE) 62 | case '2_BYTE_INTEGER' 63 | ntype = 'int16'; 64 | stype = 2; 65 | case '4_BYTE_FLOAT' 66 | ntype = 'single'; 67 | stype = 4; 68 | otherwise 69 | ntype = 'int16'; 70 | stype = 2; 71 | end 72 | 73 | % this method works but resulting cellsize not exactly constant ! 74 | %x = linspace(xll,xll + cellsize*(ncols-1),ncols); 75 | %y = linspace(yll,yll + cellsize*(nrows-1),nrows); 76 | % thus prefered the following: 77 | x = xll + (0:cellsize:(ncols-1)*cellsize); 78 | y = yll + (0:cellsize:(nrows-1)*cellsize); 79 | dx = 0; 80 | nrowsread = nrows; 81 | 82 | fid = fopen(fd,'rb',byteorder); 83 | 84 | if cropflag 85 | %crop(3) = max(crop(3),min(y)); 86 | %crop(4) = min(crop(4),max(y)); 87 | kx = find(x >= min(crop(1:2)) & x <= max(crop(1:2))); 88 | if isempty(kx) 89 | dx = -360; 90 | kx = find((x+dx) >= min(crop(1:2)) & (x+dx) <= max(crop(1:2))); 91 | end 92 | ky = find(y >= min(crop(3:4)) & y <= max(crop(3:4))); 93 | fseek(fid,(nrows - ky(end))*ncols*stype,'bof'); 94 | nrowsread = length(ky); 95 | end 96 | 97 | z = fread(fid,[ncols,nrowsread],ntype); 98 | fclose(fid); 99 | 100 | if cropflag 101 | x = x(kx)+dx; 102 | y = y(ky); 103 | z = z(kx,:); 104 | end 105 | z = flipud(z'); 106 | 107 | if nargout < 3 108 | x = struct('lon',x,'lat',y,'z',z); 109 | end 110 | 111 | %fprintf('done.\n'); 112 | 113 | -------------------------------------------------------------------------------- /latlonutm/README.md: -------------------------------------------------------------------------------- 1 | # Latitude/longitude to and from UTM coordinates precise and vectorized conversion 2 | 3 | ## ll2utm.m 4 | LL2UTM converts latitude/longitude coordinates to UTM. 5 | 6 | ## utm2ll.m 7 | UTM2LL converts Universal Transverse Mercator (UTM) East/North coordinates to latitude/longitude. 8 | 9 | 10 | Both functions are using precise formula (millimeter precision), possible user-defined datum (WGS84 is the default), and are all vectorized (no loop in the code). It means that huge matrix of points, like an entire DEM grid, can be converted very fast. 11 | 12 | Example (needs readhgt.m author's function): 13 | 14 | ```matlab 15 | X = readhgt(36:38,12:15,'merge','crop',[36.5,38.5,12.2,16],'plot'); 16 | [lon,lat] = meshgrid(X.lon,X.lat); 17 | [x,y,zone] = ll2utm(lat,lon); 18 | z = double(X.z); 19 | z(z==-32768 | z<0) = NaN; 20 | figure 21 | pcolor(x,y,z); shading flat; hold on 22 | contour(x,y,z,[0,0],'w') 23 | hold off; axis equal; axis tight 24 | xlabel('East (m)'); ylabel('North (m)') 25 | title(sprintf('Sicily - UTM zone %d WGS84',zone)) 26 | ``` 27 | 28 | loads SRTM full resolution DEM of Sicily in lat/lon (a 2400x4500 grid), converts it to UTM and plots the result. To make a regular UTM grid, you may interpolate x and y with griddata function. 29 | 30 | See "doc ll2utm" and "doc utm2ll" for syntax and help. 31 | 32 | -------------------------------------------------------------------------------- /latlonutm/ll2utm.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/latlonutm/ll2utm.m -------------------------------------------------------------------------------- /latlonutm/utm2ll.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/latlonutm/utm2ll.m -------------------------------------------------------------------------------- /polarmap/README.md: -------------------------------------------------------------------------------- 1 | # Polarized color map 2 | 3 | ## polarmap.m 4 | 5 | When representing "polarized" data for which zero and sign are meaningful (like for instance, a gravity anomalies map), it is useful to have a colormap where white stands for zero value, and greater magnitudes are plotted as colors with increasing saturation. 6 | 7 | This little function POLARMAP has two different usages: 8 | - proposes a new red-white-blue colormap that is standard for this purpose (same usage as other Matlab colormaps); 9 | - applies a zero-center white shading to any existing colormap, like JET or HSV, or user defined. 10 | 11 | ## Examples 12 | ```matlab 13 | pcolor(peaks), shading interp 14 | polarmap, colorbar 15 | ``` 16 | 17 | ```matlab 18 | polarmap(jet,0.5) 19 | ``` 20 | 21 | ![](polarmap_example.png) 22 | 23 | ## Author 24 | **François Beauducel**, [IPGP](www.ipgp.fr), [beaudu](https://github.com/beaudu), beauducel@ipgp.fr 25 | 26 | ## Documentation 27 | Type 'doc polarmap' for help and syntax. 28 | 29 | -------------------------------------------------------------------------------- /polarmap/polarmap.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/polarmap/polarmap.m -------------------------------------------------------------------------------- /polarmap/polarmap_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/polarmap/polarmap_example.png -------------------------------------------------------------------------------- /radiocover/README.md: -------------------------------------------------------------------------------- 1 | # Radio link coverage map 2 | 3 | ## radiocover.m 4 | 5 | This program computes a map of visibility from a selected point on a topography. It has been written to help the search for radio relay best location. Because it considers only direct line of sight, it gives a good estimation for possible radio link for short distances only (less than 10 km), neglecting curvature of the Earth, Fresnel zone and atmospheric refraction on radio waves propagation. The program computes the relative elevation angle of the mask for each point (the angle is null or negative if the point is visible). 6 | 7 | The function needs a digital elevation model Z and associated (X,Y) vectors or matrices of coordinates (same unit as Z), position of the point (X0,Y0), the antenna height H0 (for instance 4 m), and the hypothetic antenna height Ha on each topography points (for instance 3 m). When no output argument is given, the function plots a map of the results (color map of mask angles, and blank for visible points, see example screenshot). 8 | 9 | The script is not fully optimized because it makes a global loop on the matrix elements to compute each profile of topography... (I didn't find (yet) a way to fully vectorize the problem), so it takes some time to compute, depending on the number of element of Z. However, I found a faster algorithm (about 2 times faster), giving approximate result but usefull to process a first map. See help for syntax, and script comments for details. 10 | 11 | ## Example 12 | ```matlab 13 | [x,y,z]=peaks(100); 14 | [fx,fy]=gradient(z); 15 | z=sqrt(fx.^2+fy.^2); 16 | surf(x,y,z), shading flat, light, view(-24,74) 17 | radiocover(x,y,z,-0.84,-0.27,.05,.05) 18 | ``` 19 | 20 | ![](radiocover_example.png) 21 | 22 | ## Author 23 | **François Beauducel**, [IPGP](www.ipgp.fr), [beaudu](https://github.com/beaudu), beauducel@ipgp.fr 24 | 25 | ## Documentation 26 | Type 'doc radiocover' for help and syntax. 27 | 28 | -------------------------------------------------------------------------------- /radiocover/radiocover.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/radiocover/radiocover.m -------------------------------------------------------------------------------- /radiocover/radiocover_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/radiocover/radiocover_example.png -------------------------------------------------------------------------------- /readhgt/README.md: -------------------------------------------------------------------------------- 1 | # Import/download NASA SRTM DEM data files (.HGT) 2 | 3 | ## readhgt.m 4 | This function imports .HGT "height" binary data files from NASA SRTM global digital elevation model of Earth land, corresponding to 1x1 degree tiles of 3-arc seconds resolution (SRTM3, around 90 m) and 1-arc second (SRTM1, around 30 m) for USA territory, and returns coordinates vectors latitude and longitude, and a matrix of elevation values. 5 | 6 | The function includes an automatic download of data from the USGS SRTM webserver, so indicating latitude and longitude is sufficient to get the data and instant map plot anywhere in the World. Also some basic options as 'merge' to concatenate tiles, 'interp' for gaps (novalue) linear interpolation, 'crop' for rectangle selection inside tile(s). 7 | 8 | ## Examples 9 | - to plot a map of the Paris region, France (single tile): 10 | ```matlab 11 | readhgt(48,2) 12 | ``` 13 | ![Paris, France (SRTM3) obtained with readhgt(48,2)](readhgt_example_paris.png) 14 | 15 | - to plot a map of the Misti volcano area, Peru (SRTM1 cropped and interpolated tile). If the tiles are not already available locally, download needs a valid user/password login at NASA/EarthDATA center): 16 | ```matlab 17 | readhgt([-16.4,-16.2,-71.5,-71.3],'srtm1','login','usr','pwd','interp') 18 | ``` 19 | ![Misti volcano, (SRTM1) obtained with readhgt([-16.4,-16.2,-71.5,-71.3])](readhgt_example_misti.png) 20 | 21 | - to plot a map of Flores volcanic island, Indonesia (5 tiles): 22 | ```matlab 23 | readhgt(-9,119:123) % syntax with lat/lon tile integers 24 | % or 25 | readhgt([-9,-8,119,124]) % syntax with lat/lon box limits 26 | ``` 27 | 28 | - to download SRTM1 data of Cascade Range (27 individual tiles): 29 | ```matlab 30 | X=readhgt(40:48,-123:-121,'tiles'); 31 | ``` 32 | 33 | ![Around Seattle, USA (SRTM1) obtained with readhgt(46:47,-123:-122)](readhgt_example_seattle.png) 34 | 35 | ![East African Rift volcanoes, Tanzania (SRTM3) obtained with readhgt(-4:-3,35:36)](readhgt_example_tanzania.png) 36 | 37 | 38 | ## Author 39 | **François Beauducel**, [IPGP](www.ipgp.fr), [beaudu](https://github.com/beaudu), beauducel@ipgp.fr 40 | 41 | ## Documentation 42 | Type 'doc readhgt' for syntax, help and examples. The function needs the file "readhgt_srtm_index.txt" and get better mapping results with author's dem.m function. 43 | -------------------------------------------------------------------------------- /readhgt/readhgt.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/readhgt/readhgt.m -------------------------------------------------------------------------------- /readhgt/readhgt_example_misti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/readhgt/readhgt_example_misti.png -------------------------------------------------------------------------------- /readhgt/readhgt_example_paris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/readhgt/readhgt_example_paris.png -------------------------------------------------------------------------------- /readhgt/readhgt_example_seattle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/readhgt/readhgt_example_seattle.png -------------------------------------------------------------------------------- /readhgt/readhgt_example_tanzania.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IPGP/mapping-lib/83c0e326ca10ffdb872ce2e37c485df29c1d6408/readhgt/readhgt_example_tanzania.png --------------------------------------------------------------------------------