text: /*
   * Copyright (C) 2014 United States Government as represented by the Administrator of the
   * National Aeronautics and Space Administration. All Rights Reserved.
   */
  /**
   * @exports Shapefile
   * @version $Id: Shapefile.js 3259 2015-06-25 00:53:55Z tgaskins $
   */
  define([
          '../../geom/Angle',
          '../../error/ArgumentError',
          '../../util/ByteBuffer',
          '../../util/Color',
          '../../formats/shapefile/DBaseFile',
          '../../geom/Location',
          '../../util/Logger',
          '../../error/NotYetImplementedError',
          '../../shapes/Path',
          '../../shapes/Placemark',
          '../../shapes/PlacemarkAttributes',
          '../../shapes/Polygon',
          '../../geom/Position',
          '../../formats/shapefile/PrjFile',
          '../../layer/RenderableLayer',
          '../../shapes/ShapeAttributes',
          '../../formats/shapefile/ShapefileRecord',
          '../../formats/shapefile/ShapefileRecordMultiPoint',
          '../../formats/shapefile/ShapefileRecordNull',
          '../../formats/shapefile/ShapefileRecordPoint',
          '../../formats/shapefile/ShapefileRecordPolygon',
          '../../formats/shapefile/ShapefileRecordPolyline',
          '../../shapes/SurfacePolygon',
          '../../shapes/SurfacePolyline'
      ],
      function (Angle,
                ArgumentError,
                ByteBuffer,
                Color,
                DBaseFile,
                Location,
                Logger,
                NotYetImplementedError,
                Path,
                Placemark,
                PlacemarkAttributes,
                Polygon,
                Position,
                PrjFile,
                RenderableLayer,
                ShapeAttributes,
                ShapefileRecord,
                ShapefileRecordMultiPoint,
                ShapefileRecordNull,
                ShapefileRecordPoint,
                ShapefileRecordPolygon,
                ShapefileRecordPolyline,
                SurfacePolygon,
                SurfacePolyline) {
          "use strict";
  
          /**
           * Constructs a shapefile object for a specified shapefile URL. Call [load]{@link Shapefile#load} to retrieve the
           * shapefile and create shapes for it.
           * @alias Shapefile
           * @constructor
           * @classdesc Parses a shapefile and creates shapes representing its contents. Points in the shapefile are
           * represented by [Placemarks]{@link Placemark}, lines are represented by [Paths]{@link Path} or
           * [SurfacePolylines]{@link SurfacePolyline}, and polygons
           * are represented by [Polygons]{@link Polygon} or [SurfacePolygons]{@link SurfacePolygon}.
           * A parser completion callback may be specified and is
           * called when the shapefile is fully parsed but before shapes are created.
           * <p>
           * An attribute callback may also be specified to examine each record and configure the shape created for it.
           * This function enables the application to assign independent attributes to each
           * shape. An argument to this function provides any attributes specified in an attribute file (.dbf)
           * accompanying the shapefile. That attribute file is automatically detected, retrieved and parsed along
           * with the shapefile.
           * @param {String} url The location of the shapefile.
           * @throws {ArgumentError} If the specified URL is null or undefined.
           */
          var Shapefile = function (url) {
              if (!url) {
                  throw new ArgumentError(
                      Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "constructor", "missingUrl"));
              }
  
              // Documented in defineProperties below.
              this._url = url;
  
              // Documented in defineProperties below.
              this._shapeType = null;
  
              // Documented in defineProperties below.
              this._layer = null;
  
              // Documented in defineProperties below.
              this._shapeConfigurationCallback = this.defaultShapeConfigurationCallback;
  
              this._parserCompletionCallback = this.defaultParserCompletionCallback;
  
              // Internal use only. Intentionally not documented.
              this._buffer = null;
  
              // Internal use only. Intentionally not documented.
              this.attributeFile = new DBaseFile(url.replace(".shp", ".dbf"));
  
              // Internal use only. Intentionally not documented.
              this.projectionFile = new PrjFile(url.replace(".shp", ".prj"));
  
              this.defaultPlacemarkAttributes = new PlacemarkAttributes(null);
              this.defaultShapeAttributes = new ShapeAttributes(null);
          };
  
          Object.defineProperties(Shapefile.prototype, {
              /**
               * The shapefile URL as specified to this shapefile's constructor.
               * @memberof Shapefile.prototype
               * @type {String}
               * @readonly
               */
              url: {
                  get: function () {
                      return this._url;
                  }
              },
  
              /**
               * The shape type of the shapefile. The type can be one of the following:
               * <ul>
               *     <li>WorldWind.POINT</li>
               *     <li>WorldWind.MULTI_POINT</li>
               *     <li>WorldWind.POLYLINE</li>
               *     <li>WorldWind.POLYGON</li>
               * </ul>
               * This value is defined during shapefile loading.
               * @memberof Shapefile.prototype
               * @type {String}
               * @readonly
               */
              shapeType: {
                  get: function () {
                      return this._shapeType;
                  }
              },
  
              /**
               * The layer containing the shapes representing the records in this shapefile, as specified to this
               * shapefile's constructor or created by the constructor if no layer was specified.
               * @memberof Shapefile.prototype
               * @type {RenderableLayer}
               * @readonly
               */
              layer: {
                  get: function () {
                      return this._layer;
                  }
              },
  
              /**
               * The completion callback specified to [load]{@link Shapefile#load}. This function is called when
               * shapefile parsing is done but before creating shapes for the shapefile. It's single argument is
               * this shapefile.
               * @memberof Shapefile.prototype
               * @type {Function}
               * @default [defaultParserCompletionCallback]{@link Shapefile#defaultParserCompletionCallback}
               * @readonly
               */
              parserCompletionCallback: {
                  get: function () {
                      return this._parserCompletionCallback;
                  }
              },
  
              /**
               * The attribute callback specified to [load]{@link Shapefile#load}.
               * See that method's description for details.
               * @memberof Shapefile.prototype
               * @type {Function}
               * @default [defaultShapeConfigurationCallback]{@link Shapefile#defaultShapeConfigurationCallback}
               * @readonly
               */
              shapeConfigurationCallback: {
                  get: function () {
                      return this._shapeConfigurationCallback;
                  }
              }
          });
  
          /**
           * Retrieves the shapefile, parses it and creates shapes representing its contents. The result is a layer
           * containing the created shapes. A function can be specified to be called when parsing is complete.
           * A function can also be specified to be called for each shapefile record so that the attributes and
           * other properties of the shape created for it can be assigned.
           *
           * @param {Function} parserCompletionCallback An optional function called when shapefile loading is complete but
           * shape creation has not begun. If none is specified,
           * [defaultParserCompletionCallback]{@link Shapefile#defaultParserCompletionCallback} is called. That function creates
           * World Wind shapes for the parsed shapefile records.
           * The single argument to the callback function is this shapefile object. When the callback function is
           * called, the layer containing the shapes is available via this shapefile's
           * [layer]{@link Shapefile#layer} property.
           *
           * @param {Function} shapeConfigurationCallback An optional function called by the addRenderablesFor*
           * methods just prior to creating a shape for the indicated shapefile record. This function
           * can be used to assign attributes to newly created shapes. The callback function's first argument is an
           * object containing the properties read from the corresponding shapefile attributes file, if any.
           * This file, which has a .dbf suffix, is automatically detected, retrieved and parsed if it exists. The second
           * argument to the callback function is the {@link ShapefileRecord} currently being operated on. The return
           * value of the callback function must be either an object whose properties define attributes and other
           * information for the shape, or null, in which case no shape is created for that record. See the following
           * methods for descriptions of the configuration properties they recognize:
           * <ul>
           *     <li>[addRenderablesForPoint]{@link Shapefile#addRenderablesForPoints}</li>
           *     <li>[addRenderablesForMultiPoint]{@link Shapefile#addRenderablesForMultiPoints}</li>
           *     <li>[addRenderablesForPolylines]{@link Shapefile#addRenderablesForPolylines}</li>
           *     <li>[addRenderablesForPolygons]{@link Shapefile#addRenderablesForPolygons}</li>
           * </ul>
           *
           * @param {RenderableLayer} layer A {@link RenderableLayer} to hold the shapes created for each shapefile
           * record. If null, a new layer is created and assigned to this object's [layer]{@link Shapefile#layer}
           * property.
           */
          Shapefile.prototype.load = function (parserCompletionCallback, shapeConfigurationCallback, layer) {
              if (parserCompletionCallback) {
                  this._parserCompletionCallback = parserCompletionCallback;
              }
  
              if (shapeConfigurationCallback) {
                  this._shapeConfigurationCallback = shapeConfigurationCallback;
              }
  
              this._layer = layer || new RenderableLayer();
  
              // Load primary and secondary files in the following order:
              //      1) Projection file,
              //      2) Attribute file, and
              //      3) Shapefile.
              // This is done because the projection and attribute files modify the interpretation of the shapefile.
              var projectionFileCallback = (function () {
                  var attributeFileCallback = (function () {
                      this.requestUrl(this.url);
                  }).bind(this);
  
                  this.attributeFile.load(attributeFileCallback);
              }).bind(this);
  
              this.projectionFile.load(projectionFileCallback);
          };
  
          /**
           * The default parser completion callback, called if none was specified to the [load]{@link Shapefile#load} method.
           * This default callback merely calls [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}
           * to create shapes for this shapefile's records.
           * @param {Shapefile} shapefile This shapefile.
           */
          Shapefile.prototype.defaultParserCompletionCallback = function (shapefile) {
              this.addRenderablesForShapefile(this.layer);
          };
  
          /**
           * The default [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} for this shapefile.
           * It is called if none was specified to the [load]{@link Shapefile#load} method.
           * This method assigns shared, default attributes to the shapes created for each record. Any changes to these
           * attributes will have an effect in all shapes created by this shapefile.
           * <p>
           * For polygon records, the record's attributes are checked for an attribute named "height", "Height",
           * or "HEIGHT". If found, the returned shape configuration contains a height property holding the
           * value associated with the record attribute. This causes the default shape creation function,
           * [addRenderablesForPolygons]{@link Shapefile#addRenderablesForPolygons}, to create a
           * {@link Polygon} with its extrude property set to true and position altitudes equal to the specified
           * height value.
           * <p>
           * For all records, the record's attributes are checked for an attribute named "name", "Name" or "NAME".
           * If found, the returned shape configuration contains a name property holding the value associated with
           * the record attribute. This value is specified as the label displayName property for all shapes created.
           * For {@link Placemark} shapes it is also specified as the placemark label.
           * It is specified as the displayName for all other shapes.
           *
           * @param {{}} attributes An object containing the attribute-value pairs found in the database file
           * associated with this shapefile. See [load]{@link Shapefile#load} for more information.
           * @param {ShapefileRecord} record The current shapefile record.
           * @returns {{}} An object with properties as described above.
           */
          Shapefile.prototype.defaultShapeConfigurationCallback = function (attributes, record) {
              var configuration = {};
  
              var name = attributes.values.name || attributes.values.Name || attributes.values.NAME;
              if (name) {
                  configuration.name = name;
              }
  
              if (record.isPointType()) {
                  configuration.attributes = this.defaultPlacemarkAttributes;
              } else if (record.isMultiPointType()) {
                  configuration.attributes = this.defaultPlacemarkAttributes;
              } else if (record.isPolylineType()) {
                  configuration.attributes = this.defaultShapeAttributes;
              } else if (record.isPolygonType()) {
                  configuration.attributes = this.defaultShapeAttributes;
                  var height = attributes.values.height || attributes.values.Height || attributes.values.HEIGHT;
                  if (height) {
                      configuration.height = height;
                  }
              }
  
              return configuration;
          };
  
          /**
           * Iterates over this shapefile's records and creates shapes for them. See the following methods for the
           * details of the shapes created and their use of the
           * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback}:
           * <ul>
           *     <li>[addRenderablesForPoints]{@link Shapefile#addRenderablesForPoints}</li>
           *     <li>[addRenderablesForMultiPoints]{@link Shapefile#addRenderablesForMultiPoints}</li>
           *     <li>[addRenderablesForPolylines]{@link Shapefile#addRenderablesForPolylines}</li>
           *     <li>[addRenderablesForPolygons]{@link Shapefile#addRenderablesForPolygons}</li>
           * </ul>
           * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
           * @throws {ArgumentError} If the specified layer is null or undefined.
           */
          Shapefile.prototype.addRenderablesForShapefile = function (layer) {
              if (!layer) {
                  throw new ArgumentError(
                      Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForShapefile", "missingLayer"));
              }
  
              if (this.isPointType()) {
                  this.addRenderablesForPoints(layer);
              } else if (this.isMultiPointType()) {
                  this.addRenderablesForMultiPoints(layer);
              } else if (this.isPolylineType()) {
                  this.addRenderablesForPolylines(layer);
              } else if (this.isPolygonType()) {
                  this.addRenderablesForPolygons(layer);
              }
          };
  
          /**
           * Iterates over this shapefile's records and creates {@link Placemark}s for the shapefile's point records.
           * One placemark is created for each record.
           * Applications typically do not call this method directly. It is called by
           * [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}.
           * <p>
           * This method invokes this shapefile's
           * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} once for each record.
           * If that function returns null, the record is skipped. If it returns non-null, the returned value is
           * assumed to be an object with any or all of the following optional properties:
           * <ul>
           *     <li><i>attributes:</i> A {@link PlacemarkAttributes} object to assign to the placemark created
           *     for the record.</li>
           *     <li><i>highlightAttributes:</i> A {@link PlacemarkAttributes} object to assign to the
           *     highlight attributes of the placemark
           *     created for the record.</li>
           *     <li><i>altitudeMode:</i> The [altitude mode]{@link AbstractShape#altitudeMode} to apply to the
           *     created placemark. If not specified,
           *     [WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND} is used.</li>
           *     <li><i>name:</i> A String to assign as the created placemark's label.</li>
           *     <li><i>altitude:</i> A Number indicating the altitude of the created placemark.
           *     If not specified, the altitude of all created placemarks is 0.</li>
           *     <li><i>pickDelegate:</i> An object returned as the userObject when this feature is picked.</li>
           *     <li><i>userProperties:</i> An ad hoc object assigned to the renderable.</li>
           * </ul>
           * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
           * @throws {ArgumentError} If the specified layer is null or undefined.
           */
          Shapefile.prototype.addRenderablesForPoints = function (layer) {
              if (!layer) {
                  throw new ArgumentError(
                      Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForPoints", "missingLayer"));
              }
  
              // Note: for points, there should be only ONE part, and only ONE point per record.
              for (var record = this.next(); !!record; record = this.next()) {
                  var configuration = this.shapeConfigurationCallback(record.attributes, record),
                      altitude = (configuration && configuration.altitude) ? configuration.altitude : 0;
  
                  if (!configuration) {
                      continue;
                  }
  
                  for (var part = 0, parts = record.numberOfParts; part < parts; part += 1) {
                      var points = record.pointBuffer(part);
                      for (var idx = 0, len = points.length; idx < len; idx += 2) {
                          var longitude = points[idx],
                              latitude = points[idx + 1],
                              position = new Position(latitude, longitude, altitude),
                              placemark = new Placemark(position, false, configuration.attributes);
  
                          placemark.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                          if (configuration.highlightAttributes) {
                              placemark.highlightAttributes = configuration.highlightAttributes;
                          }
                          if (configuration.name) {
                              placemark.label = configuration.name;
                          }
                          if (configuration.pickDelegate) {
                              placemark.pickDelegate = configuration.pickDelegate;
                          }
                          if (configuration.userProperties) {
                              placemark.userProperties = configuration.userProperties;
                          }
                          layer.addRenderable(placemark);
                      }
                  }
              }
          };
  
          /**
           * Iterates over this shapefile's records and creates {@link Placemark}s for each point in the shapefile's
           * multi-point records.
           * One placemark is created for each point.
           * Applications typically do not call this method directly. It is called by
           * [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}.
           * <p>
           * This method invokes this shapefile's
           * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} once for each record.
           * If that function returns null, the record is skipped. If it returns non-null, the returned value is
           * assumed to be an object with any or all of the following optional properties:
           * <ul>
           *     <li><i>attributes:</i> A {@link PlacemarkAttributes} object to assign to the placemarks created
           *     for the record.</li>
           *     <li><i>highlightAttributes:</i> A {@link PlacemarkAttributes} object to assign to the
           *     highlight attributes of the placemarks created for the record.</li>
           *     <li><i>altitudeMode:</i> The [altitude mode]{@link AbstractShape#altitudeMode} to apply to the
           *     created placemarks. If not specified,
           *     [WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND} is used.</li>
           *     <li><i>label:</i> A String to assign as the created placemarks' label.</li>
           *     <li><i>altitude:</i> A Number indicating the altitude of the created placemarks.
           *     If not specified, the altitude of all created placemarks is 0.</li>
           *     <li><i>name:</i> A String to assign as the created placemarks' label.</li>
           *     <li><i>pickDelegate:</i> An object returned as the userObject when this feature is picked.</li>
           *     <li><i>userProperties:</i> An ad hoc object assigned to the renderable.</li>
           * </ul>
           * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
           * @throws {ArgumentError} If the specified layer is null or undefined.
           */
          Shapefile.prototype.addRenderablesForMultiPoints = function (layer) {
              if (!layer) {
                  throw new ArgumentError(
                      Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForMultiPoints", "missingLayer"));
              }
  
              // Note: for multi-points, there should only be ONE part.
              for (var record = this.next(); !!record; record = this.next()) {
                  var configuration = this.shapeConfigurationCallback(record.attributes, record),
                      altitude = (configuration && configuration.altitude) ? configuration.altitude : 0;
  
                  if (!configuration) {
                      continue;
                  }
  
                  for (var part = 0, parts = record.numberOfParts; part < parts; part += 1) {
                      var points = record.pointBuffer(part);
                      for (var idx = 0, len = points.length; idx < len; idx += 2) {
                          var longitude = points[idx],
                              latitude = points[idx + 1],
                              position = new Position(latitude, longitude, altitude),
                              placemark = new Placemark(position, false, configuration.attributes);
  
                          placemark.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                          if (configuration.highlightAttributes) {
                              placemark.highlightAttributes = configuration.highlightAttributes;
                          }
                          if (configuration.name) {
                              placemark.label = configuration.name;
                          }
                          if (configuration.pickDelegate) {
                              placemark.pickDelegate = configuration.pickDelegate;
                          }
                          if (configuration.userProperties) {
                              placemark.userProperties = configuration.userProperties;
                          }
                          layer.addRenderable(placemark);
                      }
                  }
              }
          };
  
          /**
           * Iterates over this shapefile's records and creates {@link Path}s or {@link SurfacePolyline}s for the
           * shapefile's polyline records, depending on the altitude optionally returned by the
           * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback}.
           * One shape is created for each record.
           * Applications typically do not call this method directly. It is called by
           * [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}.
           * <p>
           * This method invokes this shapefile's
           * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} once for each record.
           * If that function returns null, the record is skipped. If it returns non-null, the returned value is
           * assumed to be an object with any or all of the following optional properties:
           * <ul>
           *     <li><i>attributes:</i> A {@link ShapeAttributes} object to assign to the shape created
           *     for the record.</li>
           *     <li><i>highlightAttributes:</i> A {@link ShapeAttributes} object to assign to the highlight
           *     attributes of the shape created for the record.</li>
           *     <li><i>altitudeMode:</i> The [altitude mode]{@link AbstractShape#altitudeMode} to apply to the
           *     created shape. If not specified,
           *     [WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND} is used.
           *     <li><i>altitude:</i> A Number indicating the altitude of the created shape.
           *     If unspecified or 0, a {@link SurfacePolyline} is created for the record, otherwise a
           *     {@link Path} is created.</li>
           *     <li><i>name:</i> A String to assign as the created shape's displayName property.</li>
           *     <li><i>pickDelegate:</i> An object returned as the userObject when this feature is picked.</li>
           *     <li><i>userProperties:</i> An ad hoc object assigned to the renderable.</li>
           * </ul>
           * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
           * @throws {ArgumentError} If the specified layer is null or undefined.
           */
          Shapefile.prototype.addRenderablesForPolylines = function (layer) {
              if (!layer) {
                  throw new ArgumentError(
                      Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForPolylines", "missingLayer"));
              }
  
              for (var record = this.next(); !!record; record = this.next()) {
                  var configuration = this.shapeConfigurationCallback(record.attributes, record),
                      altitude = (configuration && configuration.altitude) ? configuration.altitude : 0;
  
                  if (!configuration) {
                      continue;
                  }
  
                  for (var part = 0, parts = record.numberOfParts; part < parts; part += 1) {
                      var points = record.pointBuffer(part);
  
                      var positions = [];
  
                      for (var idx = 0, len = points.length; idx < len; idx += 2) {
                          var longitude = points[idx],
                              latitude = points[idx + 1],
                              position = !altitude ?
                                  new Location(latitude, longitude) : new Position(latitude, longitude, altitude);
  
                          positions.push(position);
                      }
  
                      var shape;
                      if (!altitude) {
                          shape = new SurfacePolyline(positions, configuration.attributes);
                      } else {
                          shape = new Path(positions, configuration.attributes);
                          shape.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                      }
  
                      if (configuration.highlightAttributes) {
                          shape.highlightAttributes = configuration.highlightAttributes;
                      }
                      if (configuration.name) {
                          shape.displayName = configuration.name;
                      }
                      if (configuration.pickDelegate) {
                          shape.pickDelegate = configuration.pickDelegate;
                      }
                      if (configuration.userProperties) {
                          shape.userProperties = configuration.userProperties;
                      }
                      layer.addRenderable(shape);
                  }
              }
          };
  
          /**
           * Iterates over this shapefile's records and creates {@link Polygon}s or {@link SurfacePolygon}s for the
           * shapefile's polygon records, depending on the altitude and height optionally returned by the
           * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback}.
           * One shape is created for each record.
           * Applications typically do not call this method directly. It is called by
           * [addRenderablesForShapefile]{@link Shapefile#addRenderablesForShapefile}.
           * <p>
           * This method invokes this shapefile's
           * [shapeConfigurationCallback]{@link Shapefile#shapeConfigurationCallback} once for each record.
           * If that function returns null, the record is skipped. If it returns non-null, the returned value is
           * assumed to be an object with any or all of the following optional properties:
           * <ul>
           *     <li><i>attributes:</i> A {@link ShapeAttributes} object to assign to the shape created
           *     for the record.</li>
           *     <li><i>highlightAttributes:</i> A {@link ShapeAttributes} object to assign to the highlight
           *     attributes of the shape created for the record.</li>
           *     <li><i>altitudeMode:</i> The [altitude mode]{@link AbstractShape#altitudeMode} to apply to the
           *     created shape. If not specified,
           *     [WorldWind.RELATIVE_TO_GROUND]{@link WorldWind#RELATIVE_TO_GROUND} is used.
           *     If the altitude is 0, this property is ignored.</li>
           *     <li><i>altitude:</i> A Number indicating the altitude of the created shape.
           *     If unspecified or 0 and the <i>height</i> property (see next line) is undefined or 0,
           *     a {@link SurfacePolygon} is created for the record, otherwise a {@link Polygon} is created.</li>
           *     <li><i>height:</i> A Number indicating polygon height. If defined and non-zero, a
           *     {@link Polygon} is created for this record with its position altitudes set to the specified height
           *     relative to ground and its [extrude]{@link Polygon#extrude} property set to true to create an
           *     extruded polygon. A height specified here overrides an altitude if both are specified.</li>
           *     <li><i>name:</i> A String to assign as the created shape's displayName property.</li>
           *     <li><i>pickDelegate:</i> An object returned as the userObject when this feature is picked.</li>
           *     <li><i>userProperties:</i> An ad hoc object assigned to the renderable.</li>
           * </ul>
           * @param {RenderableLayer} layer The layer in which to place the newly created shapes.
           * @throws {ArgumentError} If the specified layer is null or undefined.
           */
          Shapefile.prototype.addRenderablesForPolygons = function (layer) {
              if (!layer) {
                  throw new ArgumentError(
                      Logger.logMessage(Logger.LEVEL_SEVERE, "Shapefile", "addRenderablesForPolygons", "missingLayer"));
              }
  
              for (var record = this.next(); !!record; record = this.next()) {
                  var configuration = this.shapeConfigurationCallback(record.attributes, record);
  
                  var boundaries = [],
                      position,
                      height = configuration.height,
                      altitude = configuration.altitude;
  
                  for (var part = 0, parts = record.numberOfParts; part < parts; part += 1) {
                      var points = record.pointBuffer(part),
                          positions = [];
  
                      // The shapefile duplicates the first and last point in each record, but WW shapes do not
                      // require this. So skip the last point in each record.
                      for (var idx = 0, len = points.length - 2; idx < len; idx += 2) {
                          var longitude = points[idx],
                              latitude = points[idx + 1];
  
                          if (height) {
                              position = new Position(latitude, longitude, height);
                          } else if (altitude) {
                              position = new Position(latitude, longitude, altitude);
                          } else {
                              position = new Location(latitude, longitude);
                          }
  
                          positions.push(position);
                      }
  
                      boundaries.push(positions);
                  }
  
                  var shape;
                  if (height) {
                      shape = new Polygon(boundaries, configuration.attributes);
                      shape.extrude = true;
                      shape.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                  } else if (!altitude) {
                      shape = new SurfacePolygon(boundaries, configuration.attributes);
                  } else {
                      shape = new Polygon(boundaries, configuration.attributes);
                      shape.altitudeMode = configuration.altitudeMode || WorldWind.RELATIVE_TO_GROUND;
                  }
  
                  if (configuration.highlightAttributes) {
                      shape.highlightAttributes = configuration.highlightAttributes;
                  }
                  if (configuration.name) {
                      shape.displayName = configuration.name;
                  }
                  if (configuration.pickDelegate) {
                      shape.pickDelegate = configuration.pickDelegate;
                  }
                  if (configuration.userProperties) {
                      shape.userProperties = configuration.userProperties;
                  }
                  layer.addRenderable(shape);
              }
          };
  
          /**
           * Returns the next {@link ShapefileRecord} in the shapefile, or null if no more records exist. This method
           * can be used to iterate through the shapefile records. Only one such iteration is possible.
           *
           * @returns {ShapefileRecord} The next shapefile record in the shapefile, or null if no more records exist.
           */
          Shapefile.prototype.next = function () {
              while (this._buffer.position < this._buffer.limit()) {
                  var record = this.readRecord(this._buffer);
                  if (!(record instanceof ShapefileRecordNull)) {
                      return record;
                  }
              }
  
              // If you get hear, the shapefile is out of records.
              return null;
          };
  
          // Intentionally not documented.
          Shapefile.prototype.requestUrl = function (url) {
              var xhr = new XMLHttpRequest();
  
              xhr.open("GET", url, true);
              xhr.responseType = 'arraybuffer';
              xhr.onreadystatechange = (function () {
                  if (xhr.readyState === 4) {
                      if (xhr.status === 200) {
                          this._buffer = new ByteBuffer(xhr.response);
  
                          this.parse();
                      }
                      else {
                          Logger.log(Logger.LEVEL_WARNING,
                              "Shapefile retrieval failed (" + xhr.statusText + "): " + url);
                      }
  
                      if (!!this._parserCompletionCallback) {
                          this._parserCompletionCallback(this);
                      }
                  }
              }).bind(this);
  
              xhr.onerror = function () {
                  Logger.log(Logger.LEVEL_WARNING, "Shapefile retrieval failed: " + url);
  
                  if (!!this._parserCompletionCallback) {
                      this._parserCompletionCallback(this);
                  }
              };
  
              xhr.ontimeout = function () {
                  Logger.log(Logger.LEVEL_WARNING, "Shapefile retrieval timed out: " + url);
  
                  if (!!this._parserCompletionCallback) {
                      this._parserCompletionCallback(this);
                  }
              };
  
              xhr.send(null);
          };
  
          // Internal use only. Intentionally not documented.
          Shapefile.prototype.parse = function () {
              try {
                  var header = this.readHeader(this._buffer);
                  this._shapeType = header.shapeType;
              }
              catch (e) {
                  console.log(e);
              }
              finally {
              }
          };
  
          // Intentionally not documented.
          Shapefile.prototype.readHeader = function (buffer) {
              buffer.order(ByteBuffer.BIG_ENDIAN);
              var fileCode = buffer.getInt32();
              if (fileCode != Shapefile.FILE_CODE) {
                  // Let the caller catch and log the message.
                  throw new Error(Logger.log(Logger.LEVEL_SEVERE, "Shapefile header is invalid"));
              }
  
              // Skip 5 unused ints.
              buffer.skipInt32s(5);
  
              // File length.
              var lengthInWords = buffer.getInt32();
  
              // Switch to little endian for the remaining part.
              buffer.order(ByteBuffer.LITTLE_ENDIAN);
  
              // Read remaining header data.
              var version = buffer.getInt32();
              var type = buffer.getInt32();
  
              var rect = this.readBoundingRectangle(buffer);
  
              // Check whether the shape type is supported.
              var shapeType = this.getShapeType(type);
              if (shapeType == null) {
                  // Let the caller catch and log the message.
                  // TODO: ??? figure out the correct type of error to throw
                  throw new Error(Logger.log(Logger.LEVEL_SEVERE, "Shapefile type is unsupported: " + type.toString()));
              }
  
              // Assemble header
              var header = {
                  'fileLength': lengthInWords * 2, // One word = 2 bytes.
                  'version': version,
                  'shapeType': shapeType,
                  'boundingRectangle': rect.coords,
                  'normalizePoints': rect.isNormalized
              };
  
              // Skip over bounds for measures and Z.
              buffer.skipDoubles(4);
  
              return header;
          };
  
          //**************************************************************//
          //********************  Bounding Rectangle  ********************//
          //**************************************************************//
  
          /*
           * Stores a bounding rectangle's coordinates, and if the coordinates are normalized. If isNormalized is
           * true, this indicates that the original coordinate values are out of range and required
           * normalization. The shapefile and shapefile records use this to determine which records must have their point
           * coordinates normalized. Normalization is rarely needed, and this enables the shapefile to normalize only point
           * coordinates associated with records that require it.
           *
           * The Javascript implementation inherits from the following Java implementation:
           * protected static class BoundingRectangle
           * {
           *    // Four-element array of the bounding rectangle's coordinates, ordered as follows: (minY, maxY, minX, maxX).
           *    public double[] coords;
           *    // True if the coordinates are normalized, and false otherwise.
           *    public boolean isNormalized;
           * }
           *
           *  In Javascript, this is represented as the object {'coords': coords, 'isNormalized': isNormalized}
           */
  
          // Intentionally not documented.
          Shapefile.prototype.readBoundingRectangle = function (buffer) {
              if (!this.projectionFile) {
                  return this.readUnspecifiedBoundingRectangle(buffer);
              }
              else if (this.projectionFile.isGeographicCoordinateSystem()) {
                  return this.readGeographicBoundingRectangle(buffer);
              }
              else if (this.projectionFile.isProjectedCoordinateSystem()) {
                  return this.readProjectedBoundingRectangle(buffer);
              }
              else {
                  return this.readUnspecifiedBoundingRectangle(buffer);
              }
          };
  
          // Intentionally not documented.
          Shapefile.prototype.readUnspecifiedBoundingRectangle = function (buffer) {
              // Read the bounding rectangle coordinates in the following order: minY, maxY, minX, maxX.
              var coords = this.readBoundingRectangleCoordinates(buffer);
              return {'coords': coords, 'isNormalized': false};
          };
  
          // Intentionally not documented.
          Shapefile.prototype.readGeographicBoundingRectangle = function (buffer) {
              // Read the bounding rectangle coordinates in the following order: minLat, maxLat, minLon, maxLon.
              var coords = this.readBoundingRectangleCoordinates(buffer),
                  isNormalized = false,
                  normalizedLat = 0;
  
              // The bounding rectangle's min latitude exceeds -90. Set the min latitude to -90. Correct the max latitude if
              // the normalized min latitude is greater than the max latitude.
              if (coords[0] < -90) {
                  normalizedLat = Angle.normalizedDegreesLatitude(coords[0]);
  
                  coords[0] = 90;
                  isNormalized = true;
  
                  if (coords[1] < normalizedLat) {
                      coords[1] = normalizedLat;
                  }
              }
  
              // The bounding rectangle's max latitude exceeds +90. Set the max latitude to +90. Correct the min latitude if
              // the normalized max latitude is less than the min latitude.
              if (coords[1] > 90) {
                  normalizedLat = Angle.normalizedDegreesLatitude(coords[1]);
  
                  coords[1] = 90;
                  isNormalized = true;
  
                  if (coords[0] > normalizedLat)
                      coords[0] = normalizedLat;
              }
  
              // The bounding rectangle's longitudes exceed +-180, therefore the rectangle spans the international
              // dateline. Set the longitude bound to (-180, 180) to contain the dateline spanning rectangle.
              if (coords[2] < -180 || coords[3] > 180) {
                  coords[2] = -180;
                  coords[3] = 180;
                  isNormalized = true;
              }
  
              return {'coords': coords, 'isNormalized': isNormalized};
          };
  
          // Intentionally not documented.
          Shapefile.prototype.readProjectedBoundingRectangle = function (buffer) {
              throw new NotYetImplementedError(
                  Logger.log(Logger.LEVEL_SEVERE, "Shapefile.readProjectedBoundingRectangle() not yet implemented"));
  
              // TODO: complete the implementation; the Java implementation is summarized below.
              //Object o = this.getValue(AVKey.PROJECTION_NAME);
              //
              //if (AVKey.PROJECTION_UTM.equals(o)) {
              //    // Read the bounding rectangle coordinates in the following order: minEast, minNorth, maxEast, maxNorth.
              //    var coords = ShapefileUtils.readDoubleArray(buffer, 4);
              //    // Convert the UTM bounding rectangle to a geographic bounding rectangle. The zone and hemisphere parameters
              //    // have already been validated in validateBounds.
              //    var zone = (Integer) this.getValue(AVKey.PROJECTION_ZONE);
              //    var hemisphere = (String) this.getValue(AVKey.PROJECTION_HEMISPHERE);
              //    Sector sector = Sector.fromUTMRectangle(zone, hemisphere, coords[0], coords[2], coords[1], coords[3]);
              //    // Return an array with bounding rectangle coordinates in the following order: minLon, maxLon, minLat, maxLat.
              //    BoundingRectangle rect = new BoundingRectangle();
              //    rect.coords = sector.toArrayDegrees();
              //    return rect;
              //}
              //else {
              //    // The Shapefile's coordinate system projection is unsupported. This should never happen because the
              //    // projection is validated during initialization, but we check anyway. Let the caller catch and log the
              //    // message.
              //    throw new Error(Logger.log(Logger.LEVEL_SEVERE, "Shapefile has an unsupported projection"));
              //}
          };
  
          // Intentionally not documented.
          Shapefile.prototype.readBoundingRectangleCoordinates = function (buffer) {
              // Read the bounding rectangle coordinates in the following order: minX, minY, maxX, maxY.
              var minx = buffer.getDouble(),
                  miny = buffer.getDouble(),
                  maxx = buffer.getDouble(),
                  maxy = buffer.getDouble();
  
              // Return an array with bounding rectangle coordinates in the following order: minY, maxY, minX, maxX.
              return [miny, maxy, minx, maxx];
          };
  
          //**************************************************************//
          //********************  Shape Records  *************************//
          //**************************************************************//
  
          // Intentionally not documented.
          Shapefile.prototype.readRecord = function (buffer) {
              // The buffer current position is assumed to be set at the start of the record and will be set to the
              // start of the next record after this method has completed.
              var record = this.createRecord(buffer);
  
              if (record != null) {
                  // Read the record's attribute data.
                  if (this.attributeFile != null && this.attributeFile.hasNext()) {
                      var attributes = this.attributeFile.nextRecord();
                      record.setAttributes(attributes);
                  }
              }
  
              return record;
          };
  
          // Intentionally not documented.
          Shapefile.prototype.createRecord = function (buffer) {
              // Select proper record class
              if (this.isNullType()) {
                  return this.createNull(buffer);
              }
              else if (this.isPointType()) {
                  return this.createPoint(buffer);
              }
              else if (this.isMultiPointType()) {
                  return this.createMultiPoint(buffer);
              }
              else if (this.isPolygonType()) {
                  return this.createPolygon(buffer);
              }
              else if (this.isPolylineType()) {
                  return this.createPolyline(buffer);
              }
  
              return null;
          };
  
          // Intentionally not documented.
          Shapefile.prototype.createNull = function (buffer) {
              return new ShapefileRecordNull(this, buffer);
          };
  
          // Intentionally not documented.
          Shapefile.prototype.createPoint = function (buffer) {
              return new ShapefileRecordPoint(this, buffer);
          };
  
          // Intentionally not documented.
          Shapefile.prototype.createMultiPoint = function (buffer) {
              return new ShapefileRecordMultiPoint(this, buffer);
          };
  
          // Intentionally not documented.
          Shapefile.prototype.createPolyline = function (buffer) {
              return new ShapefileRecordPolyline(this, buffer);
          };
  
          // Intentionally not documented.
          Shapefile.prototype.createPolygon = function (buffer) {
              return new ShapefileRecordPolygon(this, buffer);
          };
  
          // Intentionally not documented.
          Shapefile.prototype.getShapeType = function (shapeType) {
              // Cases commented out indicate shape types not implemented
              switch (shapeType) {
                  case 0:
                      return Shapefile.NULL;
                  case 1:
                      return Shapefile.POINT;
                  case 3:
                      return Shapefile.POLYLINE;
                  case 5:
                      return Shapefile.POLYGON;
                  case 8:
                      return Shapefile.MULTI_POINT;
  
                  case 11:
                      return Shapefile.POINT_Z;
                  case 13:
                      return Shapefile.POLYLINE_Z;
                  case 15:
                      return Shapefile.POLYGON_Z;
                  case 18:
                      return Shapefile.MULTI_POINT_Z;
  
                  case 21:
                      return Shapefile.POINT_M;
                  case 23:
                      return Shapefile.POLYLINE_M;
                  case 25:
                      return Shapefile.POLYGON_M;
                  case 28:
                      return Shapefile.MULTI_POINT_M;
  
  //            case 31:
  //                return Shapefile.SHAPE_MULTI_PATCH;
  
                  default:
                      return null; // unsupported shape type
              }
          };
  
          //**************************************************************//
          //********************  Utilities  *****************************//
          //**************************************************************//
  
          /**
           * Indicates whether this shapefile contains optional measure values.
           *
           * @return {Boolean} True if this shapefile is one that contains measure values.
           */
          Shapefile.prototype.isMeasureType = function () {
              return Shapefile.measureTypes.hasOwnProperty(this._shapeType);
          };
  
          /**
           * Indicates whether this shapefile contains Z values.
           *
           * @return {Boolean} True if this shapefile contains Z values.
           */
          Shapefile.prototype.isZType = function () {
              return Shapefile.zTypes.hasOwnProperty(this._shapeType);
          };
  
          /**
           * Indicates whether this shapefile is [Shapefile.NULL]{@link Shapefile#NULL}.
           *
           * @return {Boolean} True if this shapefile is a null type.
           */
          Shapefile.prototype.isNullType = function () {
              return this._shapeType === Shapefile.NULL;
          };
  
          /**
           * Indicates whether this shapefile is either
           * [Shapefile.POINT]{@link Shapefile#POINT},
           * [Shapefile.POINT_M]{@link Shapefile#POINT_M}
           * or [Shapefile.POINT_Z]{@link Shapefile#POINT_Z}.
           *
           * @return {Boolean} True if the shapefile is a point type.
           */
          Shapefile.prototype.isPointType = function () {
              return Shapefile.pointTypes.hasOwnProperty(this._shapeType);
          };
  
          /**
           * Indicates whether this shapefile is either
           * [Shapefile.MULTI_POINT]{@link Shapefile#MULTI_POINT},
           * [Shapefile.MULTI_POINT_M]{@link Shapefile#MULTI_POINT_M}
           * or [Shapefile.MULTI_POINT_Z]{@link Shapefile#MULTI_POINT_Z}.
           *
           * @return {Boolean} True if this shapefile is a multi-point type.
           */
          Shapefile.prototype.isMultiPointType = function () {
              return Shapefile.multiPointTypes.hasOwnProperty(this._shapeType);
          };
  
          /**
           * Indicates whether this shapefile is either
           * [Shapefile.POLYLINE]{@link Shapefile#POLYLINE},
           * [Shapefile.POLYLINE_M]{@link Shapefile#POLYLINE_M}
           * or [Shapefile.POLYLINE_Z]{@link Shapefile#POLYLINE_Z}.
           *
           * @return {Boolean} True if this shapefile is a polyline type.
           */
          Shapefile.prototype.isPolylineType = function () {
              return Shapefile.polylineTypes.hasOwnProperty(this._shapeType);
          };
  
          /**
           * Indicates whether this shapefile is either
           * [Shapefile.POLYGON]{@link Shapefile#POLYGON},
           * [Shapefile.POLYGON_M]{@link Shapefile#POLYGON_M}
           * or [Shapefile.POLYGON_Z]{@link Shapefile#POLYGON_Z}.
           *
           * @return {Boolean} True if this shapefile is a polygon type.
           */
          Shapefile.prototype.isPolygonType = function () {
              return Shapefile.polygonTypes.hasOwnProperty(this._shapeType);
          };
  
          Shapefile.NULL = "null";
          Shapefile.POINT = "point";
          Shapefile.MULTI_POINT = "multiPoint";
          Shapefile.POLYLINE = "polyline";
          Shapefile.POLYGON = "polygon";
  
          Shapefile.POINT_M = Shapefile.POINT + "M";
          Shapefile.MULTI_POINT_M = Shapefile.MULTI_POINT + "M";
          Shapefile.POLYLINE_M = Shapefile.POLYLINE + "M";
          Shapefile.POLYGON_M = Shapefile.POLYGON + "M";
  
          Shapefile.POINT_Z = Shapefile.POINT + "Z";
          Shapefile.MULTI_POINT_Z = Shapefile.MULTI_POINT + "Z";
          Shapefile.POLYLINE_Z = Shapefile.POLYLINE + "Z";
          Shapefile.POLYGON_Z = Shapefile.POLYGON + "Z";
  
          Shapefile.SHAPE_MULTI_PATCH = "multiPatch";
  
          // Internal use only. Intentionally not documented.
          Shapefile.measureTypes = {
              pointM: Shapefile.POINT_M,
              pointZ: Shapefile.POINT_Z,
              multiPointM: Shapefile.MULTI_POINT_M,
              multiPointZ: Shapefile.MULTI_POINT_Z,
              polylineM: Shapefile.POLYLINE_M,
              polylineZ: Shapefile.POLYLINE_Z,
              polygonM: Shapefile.POLYGON_M,
              polygonZ: Shapefile.POLYGON_Z
          };
  
          // Internal use only. Intentionally not documented.
          Shapefile.zTypes = {
              pointZ: Shapefile.POINT_Z,
              multiPointZ: Shapefile.MULTI_POINT_Z,
              polylineZ: Shapefile.POLYLINE_Z,
              polygonZ: Shapefile.POLYGON_Z
          };
  
          // Internal use only. Intentionally not documented.
          Shapefile.pointTypes = {
              point: Shapefile.POINT,
              pointZ: Shapefile.POINT_Z,
              pointM: Shapefile.POINT_M
          };
  
          // Internal use only. Intentionally not documented.
          Shapefile.multiPointTypes = {
              multiPoint: Shapefile.MULTI_POINT,
              multiPointZ: Shapefile.MULTI_POINT_Z,
              multiPointM: Shapefile.MULTI_POINT_M
          };
  
          // Internal use only. Intentionally not documented.
          Shapefile.polylineTypes = {
              polyline: Shapefile.POLYLINE,
              polylineZ: Shapefile.POLYLINE_Z,
              polylineM: Shapefile.POLYLINE_M
          };
  
          // Internal use only. Intentionally not documented.
          Shapefile.polygonTypes = {
              polygon: Shapefile.POLYGON,
              polygonZ: Shapefile.POLYGON_Z,
              polygonM: Shapefile.POLYGON_M
          };
  
          // Intentionally not documented.
          Shapefile.FILE_CODE = 0x0000270A;
  
          return Shapefile;
      }
  );