summaryrefslogtreecommitdiff
path: root/inst/mapshow.m
blob: 013a1235484fd1f983e8a760fe27d75330a43662 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
## Copyright (C) 2014-2020 Philip Nienhuis
## 
## This program is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 3 of the License, or
## (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*- 
## @deftypefn {Function File} @var{H} = mapshow (@var{data})
## @deftypefnx {Function File} @var{H} = mapshow (@var{data}, @var{clr})
## @deftypefnx {Function File} @var{H} = mapshow (@var{data}, @var{clr}, ...)
## @deftypefnx {Function File} @var{H} = mapshow (@var{data}, ...)
## @deftypefnx {Function File} @var{H} = mapshow (@var{X}, @var{Y})
## @deftypefnx {Function File} @var{H} = mapshow (@var{X}, @var{Y}, @var{clr})
## Draw a map based on raster or shapefile data.
##
## @var{data} can be: 
##
## @itemize
## @item The filename of a GIS raster file (any file supported by the GDAL
## library) or of an ArcGIS shapefile.  mapshow will invoke rasterread.m
## and rasterdraw.m.
##
## @item A raster band struct created by rasterread.m; in that case the
## corresponding raster info struct (also made by rasterread.m) is required
## as second input argument.
##
## @item A struct created by shaperead.m.  @var{data} can be a mapstruct or
## an Octave-style shape struct.
##
## @item  The base name or full file name of an ArcGis shape file.  mapshow
## will invoke shaperead.m and shapedraw.m
## @end itemize
##
## If the first two arguments to mapshow.m contain numeric vectors, mapshow
## will simply draw the vectors as XY lines. The vectors can contain NaNs (in
## identical positions) to separate parts.
##
## For raster maps currently no further input arguments are recognized. 
## For shapefile data, optional argument @var{clr} can be a predefined color
## ("k", "c", etc.), and RGB triplet, or a 2 X 1 column vector of predefined
## colors or RGB triplets (each row containing a predefined color or triplet).
## The upper row will be used for points and lines, the lower row for solid
## shape features.  For XY data, only the first row is used.  One-character
## color codes can be preceded by one-character linestyle indicators
## (":", "-", "--", "-.") to modify the linestyle for polylines, or marker
## styles ("o", "*", ".", "+", "@", ">", "<", "s", "d", "h", "v", "^") for
## points.
##
## Any other arguments are considered graphics properties for (multi-)points,
## polylines and polygons and will be conveyed as-is to the actual plotting
## routines.
##
## Additionally, if the first argument is a shape struct, mapshow accepts a
## property-value pair "symbolspec" (minimum abbreviation "symb") with a value
## comprising a cell array containing instructions on how to display the
## shape contents.  Multiple sympolspec property/value pairs can be specified.
##
## Return argument @var{h} is a handle to the plot figure.
##
##
## Examples:
##
## @example
##   H = mapshow ("/full/path/to/map")
##   (draws a raster map and returns the figure handle in H)
## @end example
##
## @example
##   H = mapshow ("shape.shp", ":g")
##   H = mapshow ("shape.shp", "color", "g", "linestyle", ":")
##   (draws a polygon shapefile "shape.shp" with green
##    dotted lines and return figure handle in H)
## @end example
##
## @example
##   mapshow (X, Y, "k")
##   (plot vectors X and Y in black color)
## @end example
##
## @example
##   mapshow (X, Y, "-.r", "linewidth", 5)
##   (plot vectors X and Y as a dashdotted thick red line)
## @end example
##
## @example
##   mapshow (data, "symbolspec", symsp1, "symb", symsp2)
##   (draw contents of shapestruct (or mapstruct) data
##    according to the symbolspecs symsp1 and symsp2)
## @end example
##
## @seealso{geoshow, shapedraw, shapeinfo, shaperead, shapewrite, makesymbolspec, rasterread, rasterdraw, rasterinfo}
## @end deftypefn

## Author: Philip Nienhuis <prnienhuis@users.sf.net>
## Created: 2014-11-17 after a suggestion by Carnë Draug
## Updates:
## 2014-2015  many many fixes
## 2015-02-04 Relax check for mapstructs; don't test BoundingBox (Point shapes)
## 2015-02-11 Fix minor syntax error in do_symspecs rule check

function h = mapshow (varargin)

  ## Check "hold" state; creates a new figure if none was present
  if (ishold())
    old_holdstate = "on";
  else
    old_holdstate = "off";
  endif
  hold on;

  if ischar (varargin{1})
    [~, ~, ext] = fileparts (varargin{1});
    if (strncmpi (ext, ".shp", 4))
      ## Directly plot shapefile. Strip away symbolspecs
      isym = find (strncmpi ("symbolspec", varargin(2:end), 4));
      if (! isempty (isym))
        warning ("mapshow.m: ignoring symbolspecs when drawing .shp directly");
      endif
      varargin(isym:isym+1) = [];
      h = shapedraw (varargin{:});
    else
      ## Assume any raster file
      h = rasterdraw (varargin{1});
    endif

  elseif (nargin > 1 && israster (varargin{1}) && isstruct (varargin{2}))
    ## Assume raster structs
    h = rasterdraw (varargin{:});

  elseif (isshape (varargin{1}))
    ## Assume a shape struct or a shape file name. Get optional symbolspec
    isym = find (strncmpi("symbolspec", varargin(2:end), 4));
    if (! isempty (isym))
      ## FIXME move this stanze into a separate subfunction
      ## Found symbolspec. Check if it can be invoked
      if (ischar (varargin{1}))
        ## Symbolspec not applicable to shape file argument
        error ("mapshow: symbolspec not supported when plotting shape files directly\n");
      endif
      ## First get & check geometry
      symspecs = {varargin{isym+2}};
      h = do_symspecs (varargin{1}, symspecs, old_holdstate);
    else
      ## No symbolspec to process; plot shape(file) directly
    endif

  elseif (nargin >= 2 && isvector (varargin{1}) && isvector (varargin{2}))
    ## Assume args #1 & #2 are lines. Find optional color argument.
    if (nargin >= 3)
      if (ischar (varargin{3}))
        ## Assume arg #3 is a valid color name or character like 'b'
        h = plot (varargin{1}, varargin{2}, varargin{3});
      elseif (isnumeric (varargin{3} && size (varargin{3}, 2) == 3))
        ## Assume arg#3 is a valid color triplet
        h = plot (varargin{1}, varargin{2}, "color", varargin{3});
      else
        error ("mapshow.m: color argment expected for arg. #3\n");
      endif
      axis equal;
    else
      ## Plot args #1 & #2 without further ado
      h = plot (varargin{1}, varargin{2}, "color", [0.6 0.6 0.6]);
    endif

  else
    error ("mapshow: only plotting of shapes or vector data is implemented\n");
  endif

  ## Reset hold state
  hold (old_holdstate);

  axis equal;
  
  if (! nargout)
    h = 0;
  endif

endfunction


##--------------------------------------------------------------------------
## Copyright (C) 2014,2015 Philip Nienhuis <prnienhuis@users.sf.net>

function retval = isshape (s)

  retval = false;
  ## Check if s is a recognized shape file struct; just a brief check
  if (isstruct (s))
    ## Yep. Find out what type
    fldn = fieldnames (s);
    if (all (ismember ({"vals", "shpbox"}, fldn)))
      ## Assume it is an Octave-style struct read by shaperead
      retval = 2;
    elseif (all (ismember ({"Geometry", "X"},  fldn)))
      ## Assume it is a Matlab-style mapstruct
      retval = 1;
    endif
  endif
 
endfunction


##--------------------------------------------------------------------------
## Copyright (C) 2015 Philip Nienhuis <prnienhuis@users.sf.net>

function retval = israster (s)

  retval = 0;
  ## Check if s is a recognized raster struct; just a brief check
  if (isstruct (s))
    ## Yep. Find out what type
    fldn = fieldnames (s);
    if (ismember ("data", fldn) && ismatrix (s.data))
      ## Assume it is an Octave-style struct read by shaperead
      retval = 1;
    endif
  endif
 
endfunction


##--------------------------------------------------------------------------
## Copyright (C) 2015 Philip Nienhuis <prnienhuis@users.sf.net>
##
## Process symbolspecs one by one

function h = do_symspecs (shp, symspecs)

  for jj=1:numel (symspecs);
    symspec = symspecs{jj};
    geom =  lower (symspec {1});
    if (! ischar (geom))
      error ("mapshow: char argument expected for Geometry field in symbolspec\n");
    elseif (! ismember (lower (geom), {"point", "multipoint", "line", ...
                                       "polyline", "polygon", "patch"}))
      error ("mapshow: unknown Geometry: %s in symbolspec\n", geom);
    endif
    for ii=2:numel (symspec)
      rule = symspec{ii};
      if (! ischar (rule{1}))
        warning ("mapshow: char string expected for attribute of rule %d in symbolspec\n", ii-1);
      endif
      ## Get property or attribute
      if (strcmpi (rule{1}, "default"))
        ## Rule applies to all shape features with geom. Plot shape features
        if (isshape (shp) == 1)
          ## mapstruct shapes are allowed to be heterogeneous
          gdx = find (strcmpi (geom, {shp.Geometry}));
          h = shapedraw (shp(gdx), rule(3:end){:});
        elseif (isshape (shp) == 2)
          h = shapedraw (shp, rule(3:end){:});
        else
          error ("mapshow: improper shape struct type\n");
        end

      elseif (isshape (shp) == 1)
        ## ML mapstruct type shape struct. Apply to proper geometry features
        gdx = find (strcmpi (geom, {shp.Geometry}));
        ## Rule applies to one specific attribute. Check if it exists
        if (ismember (rule{1}, fieldnames (shp)))
          ## Try to apply rule. We need try-catch to catch non-matching classes
          if (islogical (rule{2}))
            ## Find attributes that are true. FIXME catches zero attributes too
            try
              idx = find ([shp(gdx).(rule{1})]);
            catch
              warning ("mapshow: rule %d not applicable to atribute %s\n", ...
                        ii-1, rule{1});
            end_try_catch
          elseif (isnumeric (rule{2}))
            ## Check which shape features have attribute values in the range
            minr = min (rule{2});
            maxr = max (rule{2});
            try
              idx = find ([shp(gdx).(rule{1})] >= minr & ...
                          [shp(gdx).(rule{1})] <= maxr);
            catch
              warning ("mapshow: rule %d not applicable to atribute %s\n", ...
                        ii-1, rule{1});
            end_try_catch
          elseif (ischar (rule{2}))
            ## Match strings
            try
              idx = find (strcmp (rule{2}, {shp(gdx).(rule{1})}));
            catch
              warning ("mapshow: rule %d not applicable to atribute %s\n", ...
                        ii-1, rule{1});
            end_try_catch
          endif
          ## Plot shape features
          h = shapedraw (shp(gdx(idx)), rule(3:end){:});
        else
          ## Attribute not found
          warning ("mapshow: attribute '%s' in rule #%d not found\n", rule(ii){1});
        endif

      elseif (isshape (shp) == 2)
        ## Oct-style shp struct. Prepare indexing into coords
        shp.idx = [ shp.idx; (size(shp.vals, 1) + 2) ];
        ## Some fields are not explicitly in the shape
        switch rule{1}
          ## Coordinates
          case "X"
            shp_field = shp.vals(:, 1);
          case "Y"
            shp_field = shp.vals(:, 2);
          case "Z"
            shp_field = shp.vals(:, 3);
          case "M"
            ## Measure
            shp_field = shp.vals(:, 4);
          case "npt"
            ## Nr. or points/vertices
            shp_field = shp.npt;
          case "npr"
            ## Nr. of parts
            try
              shp_field = cellfun (@numel, shp.npr);
            catch
              warning ("mapshow: rule %d attribute 'npr' not found\n", ii-1);
              shp_field = NaN;
            end_try_catch
          #case "bbox"
          #  shp_field = shp.bbox;
          otherwise
            ## "Regular" attributes
            if (ismember (rule{1}, fieldnames(shp)))
              shp_field = shp.(rule{1});
            else
              warning ("mapshow: rule %d attribute '%s' not found\n", ...
                       ii-1, rule{1});
            endif
        endswitch
        ## Try to apply rule. We need try-catch to catch non-matching classes
        if (islogical (rule{2}))
          ## Find attributes that are true. FIXME catches zero attributes too
          try
            idx = find (shp_field);
          catch
            warning ("mapshow: rule %d not applicable to atribute %s\n", ...
                      ii-1, rule{1});
          end_try_catch
        elseif (isnumeric (shp_field))
          ## Check which shape features have attribute values in the range
          minr = min (rule{2});
          maxr = max (rule{2});
          if (ismember (rule{1}, {"X", "Y", "Z", "M"}))
            ##  Multiple attribute values per shape feature (polylines, ...)
            idx = [];
            try
              for jj=1:numel (shp.idx)
                ## Include all polylines/-gons/multipatches with at least
                ## one value in the range
                idx = [idx; (any ( ...
                            shp_field(shp.idx(jj:shp.idx(jj+1)-2)) >= minr & ...
                            shp_field(shp.idx(jj:shp.idx(jj+1)-2)) <= maxr)) ];
              endfor
            catch
              warning ("mapshow: rule %d not applicable to atribute %s\n", ...
                     ii-1, rule{1});
            end_try_catch
          else
            ## Single attribute values per shape feature
            try
              idx = find (shp_field >= minr & ...
                          shp_field <= maxr);
            catch
              warning ("mapshow: rule %d not applicable to atribute %s\n", ...
                        ii-1, rule{1});
            end_try_catch
          endif
        elseif (iscellstr (shp_field))
          ## Match strings
          try
            idx = find (strcmp (rule{2}, shp_field));
          catch
            warning ("mapshow: rule %d not applicable to atribute %s\n", ...
                      ii-1, rule{1});
          end_try_catch
        endif
        ## Plot shape features; but first set up struct
        vals = zeros(0, 6);
        jdx = [];
        for jj=1:numel (idx)
          jdx = [ jdx ; (size (vals, 1) + 1) ];
          vals = [ vals; ...
                   shp.vals(shp.idx(idx(jj)):shp.idx(idx(jj)+1)-2, :); ...
                   NaN(1, 6) ];
        endfor
        vals(end, :) = [];
        sct.shpbox    = shp.shpbox;
        sct.vals      = vals;
        sct.bbox      = shp.bbox(idx, :);
        sct.npt       = shp.npt(idx);
        sct.npr       = shp.npr(idx);
        sct.idx       = jdx;
        sct.(rule{1}) = shp.(rule{1})(idx);
        ## Draw results
        h = shapedraw (sct, rule(3:end){:});

      else
        ## Unrecognized shape type
        error ("mapshow: improper shape struct type\n");

      endif
    endfor
  endfor

endfunction