drawstates.pro 12.5 KB
;+
; NAME:
;       DRAWSTATES
;
; PURPOSE:
;
;       Draws states in the USA in outline or as solid-color polygons
;       from a state shapefile.
;
; AUTHOR:
;
;       FANNING SOFTWARE CONSULTING
;       David Fanning, Ph.D.
;       1645 Sheely Drive
;       Fort Collins, CO 80526 USA
;       Phone: 970-221-0438
;       E-mail: david@idlcoyote.com
;       Coyote's Guide to IDL Programming: http://www.idlcoyote.com
;
; CATEGORY:

;       Utilities
;
; CALLING SEQUENCE:
;
;       DrawStates, stateFile
;
; ARGUMENTS:
;
;       stateFile:     The name of the input shapefile containing state boundaries.
;                      If undefined, the "states.shp" file in the IDL distribution is used.
;
; KEYWORDS:
;
;     ATTRIBUTE_NAME:  The name of the attribute in the file that you wish to draw.
;                      By default, this is set to the attribute name "STATE_ABBR".
;                      (In some shapefiles, the attribute might be named "STATE".)
;                      If you are unsure of the attribute names in your shapefile,
;                      use the Coyote Library program SHAPEINFO to browse the file
;                      ahead of time.
;
;     COLORS:          The name of a color to draw the state outline or polygon in. This
;                      may be a string array of the same size as STATENAMES. Color names
;                      correspond to the colors available in cgColor. By default, "Sky Blue".
;
;     FILL:            Normally, the state outline is drawn. If this keyword is set,
;                      the polygon representing the state is filled with a solid color.
;                      May be a vector of the same size as STATENAMES.
;
;     LINESTYLE:       The normal LINESTYLE keyword index to choose plotting linestyles.
;                      By default, set to 0 and solid lines. May be a vector of the same
;                      size as STATENAMES.
;
;     MINNUMVERTS:     Set this keyword to the minimum number of vertices required to actually
;                      draw a polygon. In other words, to to drawn, a polygon must have at least
;                      this number of vertices. The default value is 3.
;
;     STATENAMES:      The names of the states you wish to draw. Normally, these are two-element
;                      state abbreviations, but this will depend upon the entity attributes in your
;                      shapefile. If this keyword is undefined, then all the states in the
;                      file will be drawn. If you are unsure of the entity names, use the
;                      Coyote Library program SHAPEINFO to browse the file ahead of time.
;
;     THICK:           The line thickness. By default, 1.0.
;
; RESTRICTIONS:
;
;     It is assumed a map projection command has been issued and is in effect at
;     the time this program is called.
;
;     If STATENAMES is undefined, all states are drawn, but only a single value
;     for COLORS, FILL, LINESTYLE, and THICK is allowed.
;
;     Required Coyote Library programs:
;
;       Error_Message
;       cgColor
;
; EXAMPLE:
;
;       cgDisplay, 700, 600
;       Map_Set, 37.5, -117.5, /Albers, /IsoTropic, Limit=[30, -125, 45, -108], $
;          Position=[0.05, 0.05, 0.95, 0.95], /NoErase
;       DrawStates, Statenames=['CA', 'OR', 'WA', 'AZ', 'UT', 'ID'], Thick=1, $
;           Colors=['firebrick', 'indian red', 'indian red', 'indian red', 'steel blue', 'indian red'], $
;           Fill = [1,0,0,0,1,0]
;       Map_Grid, LatDel = 2.0, LonDel = 2.0, /Box_Axes, Color=cgColor('charcoal')
;
; MODIFICATION HISTORY:
;
;       Written by David W. Fanning, 2 April 2005.
;       Added MINNUMVERTS keyword. Modifed the way entity pointers are cleaned up. 
;          Upgraded to use Coyote Graphics. 6 Oct 2011. DWF.
;-
;******************************************************************************************;
;  Copyright (c) 2008, by Fanning Software Consulting, Inc.                                ;
;  All rights reserved.                                                                    ;
;                                                                                          ;
;  Redistribution and use in source and binary forms, with or without                      ;
;  modification, are permitted provided that the following conditions are met:             ;
;                                                                                          ;
;      * Redistributions of source code must retain the above copyright                    ;
;        notice, this list of conditions and the following disclaimer.                     ;
;      * Redistributions in binary form must reproduce the above copyright                 ;
;        notice, this list of conditions and the following disclaimer in the               ;
;        documentation and/or other materials provided with the distribution.              ;
;      * Neither the name of Fanning Software Consulting, Inc. nor the names of its        ;
;        contributors may be used to endorse or promote products derived from this         ;
;        software without specific prior written permission.                               ;
;                                                                                          ;
;  THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY        ;
;  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES    ;
;  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT     ;
;  SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,             ;
;  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED    ;
;  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         ;
;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND             ;
;  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT              ;
;  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           ;
;  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                            ;
;******************************************************************************************;
PRO DrawStates_DrawEntity, entity, COLOR=color, FILL=fill, LINESTYLE=linestyle, THICK=thick

   ; Error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      ok = Error_Message()
      IF Obj_Valid(shapefile) THEN Obj_Destroy, shapefile
      IF Ptr_Valid(entities) THEN Heap_Free, entities
      RETURN
   ENDIF

   ; Drawing is going to be done based on the shape type.
   CASE 1 OF

      ; Polygon shapes.
      entity.shape_type EQ 5 OR $    ; Polygon.
      entity.shape_type EQ 15 OR $   ; PolygonZ (ignoring Z)
      entity.shape_type EQ 25: BEGIN ; PolygonM (ignoring M)

         IF Ptr_Valid(entity.parts) THEN BEGIN
            cuts = [*entity.parts, entity.n_vertices]
            FOR j=0, entity.n_parts-1 DO BEGIN
               CASE fill OF

                  0: cgPlotS, (*entity.vertices)[0, cuts[j]:cuts[j+1]-1], $
                            (*entity.vertices)[1, cuts[j]:cuts[j+1]-1], $
                            COLOR=color, LINESTYLE=linestyle, THICK=thick

                  1: cgColorFill, (*entity.vertices)[0, cuts[j]:cuts[j+1]-1], $
                               (*entity.vertices)[1, cuts[j]:cuts[j+1]-1], $
                               COLOR=color

               ENDCASE
            ENDFOR
         ENDIF
      ENDCASE ; Polygon shapes.

      ; Polyline shapes.
      entity.shape_type EQ  3 OR $   ; PolyLine
      entity.shape_type EQ 13 OR $   ; PolyLineZ (ignoring Z)
      entity.shape_type EQ 23: BEGIN ; PolyLineM (ignoring M)

         IF Ptr_Valid(entity.parts) THEN BEGIN
            cuts = [*entity.parts, entity.n_vertices]
            FOR j=0, entity.n_parts-1 DO BEGIN
               CASE fill OF

                  0: cgPlotS, (*entity.vertices)[0, cuts[j]:cuts[j+1]-1], $
                            (*entity.vertices)[1, cuts[j]:cuts[j+1]-1], $
                            COLOR=color, LINESTYLE=linestyle, THICK=thick

                  1: cgColorFill, (*entity.vertices)[0, cuts[j]:cuts[j+1]-1], $
                               (*entity.vertices)[1, cuts[j]:cuts[j+1]-1], $
                               COLOR=color

               ENDCASE
            ENDFOR
         ENDIF
      ENDCASE ; Polyline shapes.

      ELSE: ; All other shapes fall through and are silently ignored.

   ENDCASE

END ;---------------------------------------------------------------------------------



PRO DrawStates, stateFile, $
   ATTRIBUTE_NAME=attribute_name, $
   COLORS=colors, $
   FILL=fill, $
   LINESTYLE=linestyle, $
   MINNUMVERTS=minNumVerts, $
   STATENAMES=statenames, $
   THICK=thick

   ; Error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      ok = Error_Message()
      IF Obj_Valid(shapefile) THEN Obj_Destroy, shapefile
      IF Ptr_Valid(entities) THEN Heap_Free, entities
      RETURN
   ENDIF

   ; Check parameters.
   IF N_Elements(stateFile) EQ 0 THEN BEGIN
      stateFile = Filepath(Subdirectory=['examples', 'data'], 'states.shp')
   ENDIF
   IF N_Elements(attribute_name) EQ 0 THEN attribute_name = 'STATE_ABBR' $
      ELSE attribute_name = StrUpCase(attribute_name)
   IF N_Elements(colors) EQ 0 THEN colors = 'Sky Blue'
   IF N_Elements(fill) EQ 0 THEN fill = Keyword_Set(fill)
   IF N_Elements(linestyle) EQ 0 THEN linestyle = 0
   IF N_Elements(minNumVerts) EQ 0 THEN minNumVerts = 3
   IF N_Elements(thick) EQ 0 THEN thick = 1.0
   IF N_Elements(statenames) EQ 0 THEN statenames = 'ALL'

   ; Make sure arrays have the same number of elements.
   IF N_Elements(statenames) NE 1 THEN BEGIN
      numStates = N_Elements(statenames)
      IF N_Elements(colors) EQ 1 THEN colors = Replicate(colors, numStates)
      IF N_Elements(colors) NE numStates THEN $
         Message, 'Number of COLORS does not match number of state names.'
      IF N_Elements(fill) EQ 1 THEN fill = Replicate(fill, numStates)
      IF N_Elements(fill) NE numStates THEN $
         Message, 'Number of FILL values does not match number of state names.'
      IF N_Elements(linestyle) EQ 1 THEN linestyle = Replicate(linestyle, numStates)
      IF N_Elements(linestyle) NE numStates THEN $
         Message, 'Number of LINESTYLE values does not match number of state names.'
      IF N_Elements(thick) EQ 1 THEN thick = Replicate(thick, numStates)
      IF N_Elements(thick) NE numStates THEN $
         Message, 'Number of THICK values does not match number of state names.'
   ENDIF

   ; Open the shape file and create the shape object.
   shapefile = Obj_New('IDLffShape', stateFile)
   IF Obj_Valid(shapefile) EQ 0 THEN $
      Message, 'Unable to create shape file object. Returning...'

   ; Get the attribute names from the shape file.
   shapefile -> GetProperty, ATTRIBUTE_NAMES=theNames
   theNames = StrUpCase(StrTrim(theNames, 2))

   ; Find the attribute index.
   attIndex = Where(theNames EQ attribute_name, count)
   IF count EQ 0 THEN Message, 'Unable to find attribute ' + attribute_name + ' in file. Returning...'

   ; Get all the attribute pointers from the file. These are the entities.
   entities = Ptr_New(/Allocate_Heap)
   *entities = shapefile -> GetEntity(/All, /Attributes)

   ; Cycle through each entity and draw it, if required. Free each pointer
   ; as you go, because it take at least 10 times as long to free them with
   ; HEAP_FREE at the end if you don't do this!
   FOR j=0,N_Elements(*entities)-1 DO BEGIN
      thisEntity = (*entities)[j]
      theState = StrUpCase(StrTrim((*thisEntity.attributes).(attIndex), 2))
      index = Where(stateNames EQ theState, test)
      IF stateNames[0] EQ 'ALL' THEN BEGIN
         index = 0
         test = 1
      ENDIF
      IF (test EQ 1) THEN BEGIN
          IF N_Elements(*thisEntity.vertices) GE minNumVerts THEN BEGIN
              DrawStates_DrawEntity, (*entities)[j], Color=(colors[index])[0], $
                   Fill=(fill[index])[0], LineStyle=(linestyle[index])[0], $
                   Thick=(thick[index])[0]
          ENDIF
      ENDIF
      Ptr_Free, thisEntity.vertices
      Ptr_Free, thisEntity.measure
      Ptr_Free, thisEntity.parts
      Ptr_Free, thisEntity.part_types
      Ptr_Free, thisEntity.attributes
   ENDFOR

   ; Clean up.
   Obj_Destroy, shapefile
   Ptr_Free, entities

END ;---------------------------------------------------------------------------------