All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.formats.shapefile.Shapefile Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.formats.shapefile;

import com.jogamp.common.nio.Buffers;
import gov.nasa.worldwind.Exportable;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.exception.*;
import gov.nasa.worldwind.formats.worldfile.WorldFile;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.ogc.kml.KMLConstants;
import gov.nasa.worldwind.util.*;

import javax.xml.stream.*;
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
import java.util.logging.Level;

/**
 * Parses an ESRI Shapefile (.shp) and provides access to its contents. For details on the Shapefile format see the ESRI
 * documentation at http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf.
 * 

* The Shapefile provides a streaming interface for parsing a Shapefile's contents. The streaming interface enables * applications to read Shapefiles that do not fit in memory. A typical usage pattern is as follows: *

 * Object source = "MyShapefile.shp";
 * Shapefile sf = new Shapefile(source);
 * try
 * {
 *     while (sf.hasNext())
 *     {
 *         ShapefileRecord record = sf.nextRecord();
 *         // Interpret Shapefile record contents...
 *     }
 * }
 * finally
 * {
 *     WWIO.closeStream(sf, source);
 * }
 * 
* *

* The source Shapefile may be accompanied by an optional index file, attribute file, and projection file. Shapefile * constructors that accept a generic source such as {@link #Shapefile(Object) expect accompanying files to be in the * same logical folder as the Shapefile, have the same filename as the Shapefile, and have suffixes ".shx", ".dbf", and * ".prj" respectively. If any of these files do not exist, or cannot be read for any reason, the Shapefile opens * without that information. Alternatively, the Shapefile can be constructed by providing a direct {@link * java.io.InputStream} to any of the accompanying sources by using the InputStream based constructors, such as {@link * #Shapefile(java.io.InputStream, java.io.InputStream, java.io.InputStream, java.io.InputStream)}. *

*

Coordinate System

*

* The Shapefile's coordinate system affects how the Shapefile's point coordinates are interpreted as follows:

    *
  • Unspecified - coordinates are not changed.
  • Geographic - coordinates are validated during parsing. * Coordinates outside the standard range of +90/-90 latitude and +180/-180 longitude cause the Shapefile to throw an * exception during construction if the Shapefile's header contains an invalid coordinate, or in {@link * #readNextRecord()} if any of the Shapefile's records contain an invalid coordinate.
  • Universal Transverse * Mercator (UTM) - UTM coordinates are converted to geographic coordinates during parsing.
  • Unsupported - the * Shapefile throws a {@link gov.nasa.worldwind.exception.WWRuntimeException} during construction. *

    * The Shapefile's coordinate system can be specified in either an accompanying projection file, or by specifying the * coordinate system parameters in an {@link gov.nasa.worldwind.avlist.AVList} during Shapefile's construction. The * Shapefile gives priority to the AVList if an accompanying projection file is available and AVList projection * parameters are specified. If an accompanying projection file is available, the Shapefile attempts to parse the * projection file as an OGC coordinate system encoded in well-known text format. For details, see the OGC Coordinate * Transform Service (CT) specification at http://www.opengeospatial.org/standards/ct. * The Shapefile expects the AVList specifying its coordinate system parameters to contain the following properties: *

    • {@link gov.nasa.worldwind.avlist.AVKey#COORDINATE_SYSTEM} - either {@link * gov.nasa.worldwind.avlist.AVKey#COORDINATE_SYSTEM_GEOGRAPHIC} or {@link gov.nasa.worldwind.avlist.AVKey#COORDINATE_SYSTEM_PROJECTED}.
    • *
    • {@link gov.nasa.worldwind.avlist.AVKey#PROJECTION_ZONE} - the UTM zone (if coordinate system projection is UTM); * an integer in the range 1-60.
    • {@link gov.nasa.worldwind.avlist.AVKey#PROJECTION_HEMISPHERE} - the UTM * hemisphere (if coordinate system is UTM); either {@link gov.nasa.worldwind.avlist.AVKey#NORTH} or {@link * gov.nasa.worldwind.avlist.AVKey#SOUTH}.
    *

    * Subclasses can override how the Shapefile reads and interprets its coordinate system. Override {@link * #readCoordinateSystem()} and {@link #validateCoordinateSystem(gov.nasa.worldwind.avlist.AVList)} to change how the * Shapefile parses an accompanying projection file and validates the coordinate system parameters. Override {@link * #readBoundingRectangle(java.nio.ByteBuffer)} and {@link #readPoints(java.nio.ByteBuffer)} to change how the * Shapefile's point coordinates are interpreted according to its coordinate system. * * @author Patrick Murris * @version $Id: Shapefile.java 1171 2013-02-11 21:45:02Z dcollins $ */ public class Shapefile extends AVListImpl implements Closeable, Exportable { protected static final int FILE_CODE = 0x0000270A; protected static final int HEADER_LENGTH = 100; protected static final String SHAPE_FILE_SUFFIX = ".shp"; protected static final String INDEX_FILE_SUFFIX = ".shx"; protected static final String ATTRIBUTE_FILE_SUFFIX = ".dbf"; protected static final String PROJECTION_FILE_SUFFIX = ".prj"; protected static final String[] SHAPE_CONTENT_TYPES = { "application/shp", "application/octet-stream" }; protected static final String[] INDEX_CONTENT_TYPES = { "application/shx", "application/octet-stream" }; protected static final String[] PROJECTION_CONTENT_TYPES = { "application/prj", "application/octet-stream", "text/plain" }; public static final String SHAPE_NULL = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapeNull"; public static final String SHAPE_POINT = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePoint"; public static final String SHAPE_MULTI_POINT = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapeMultiPoint"; public static final String SHAPE_POLYLINE = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePolyline"; public static final String SHAPE_POLYGON = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePolygon"; public static final String SHAPE_POINT_M = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePointM"; public static final String SHAPE_MULTI_POINT_M = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapeMultiPointM"; public static final String SHAPE_POLYLINE_M = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePolylineM"; public static final String SHAPE_POLYGON_M = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePolygonM"; public static final String SHAPE_POINT_Z = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePointZ"; public static final String SHAPE_MULTI_POINT_Z = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapeMultiPointZ"; public static final String SHAPE_POLYLINE_Z = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePolylineZ"; public static final String SHAPE_POLYGON_Z = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapePolygonZ"; public static final String SHAPE_MULTI_PATCH = "gov.nasa.worldwind.formats.shapefile.Shapefile.ShapeMultiPatch"; protected static List measureTypes = new ArrayList(Arrays.asList( Shapefile.SHAPE_POINT_M, Shapefile.SHAPE_POINT_Z, Shapefile.SHAPE_MULTI_POINT_M, Shapefile.SHAPE_MULTI_POINT_Z, Shapefile.SHAPE_POLYLINE_M, Shapefile.SHAPE_POLYLINE_Z, Shapefile.SHAPE_POLYGON_M, Shapefile.SHAPE_POLYGON_Z )); protected static List zTypes = new ArrayList(Arrays.asList( Shapefile.SHAPE_POINT_Z, Shapefile.SHAPE_MULTI_POINT_Z, Shapefile.SHAPE_POLYLINE_Z, Shapefile.SHAPE_POLYGON_Z )); protected static class Header { public int fileCode = FILE_CODE; public int fileLength; public int version; public String shapeType; public double[] boundingRectangle; public boolean normalizePoints; } // Shapefile data. protected Header header; protected int[] index; protected CompoundVecBuffer pointBuffer; // Source streams and read parameters. protected ReadableByteChannel shpChannel; protected ReadableByteChannel shxChannel; protected ReadableByteChannel prjChannel; protected DBaseFile attributeFile; protected boolean open; /** * Indicates if the shapefile's point coordinates should be normalized. Defaults to false. This is used by Point * records to determine if its points should be normalized. MultiPoint, Polyline, and Polygon records use their * bounding rectangles to determine if they should be normalized, and therefore ignore this property. */ protected boolean normalizePoints; protected int numRecordsRead; protected int numBytesRead; protected ByteBuffer recordHeaderBuffer; protected ByteBuffer recordContentBuffer; protected MappedByteBuffer mappedShpBuffer; /** * Opens an Shapefile from a general source. The source type may be one of the following:

    • {@link * java.io.InputStream}
    • {@link java.net.URL}
    • absolute {@link java.net.URI}
    • {@link * File}
    • {@link String} containing a valid URL description or a file or resource name available on the * classpath.
    *

    * The source Shapefile may be accompanied by an optional index file, attribute file, and projection file. To be * recognized by this Shapefile, accompanying files must be in the same logical folder as the Shapefile, have the * same filename as the Shapefile, and have suffixes ".shx", ".dbf", and ".prj" respectively. If any of these files * do not exist, or cannot be read for any reason, the Shapefile opens without that information. *

    * This throws an exception if the shapefile's coordinate system is unsupported. * * @param source the source of the shapefile. * @param params parameter list describing metadata about the Shapefile, such as its map projection. * * @throws IllegalArgumentException if the source is null or an empty string. * @throws WWRuntimeException if the shapefile cannot be opened for any reason, or if the shapefile's * coordinate system is unsupported. */ public Shapefile(Object source, AVList params) { if (source == null || WWUtil.isEmpty(source)) { String message = Logging.getMessage("nullValue.SourceIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } try { this.setValue(AVKey.DISPLAY_NAME, source.toString()); if (source instanceof File) this.initializeFromFile((File) source, params); else if (source instanceof URL) this.initializeFromURL((URL) source, params); else if (source instanceof InputStream) this.initializeFromStreams((InputStream) source, null, null, null, params); else if (source instanceof String) this.initializeFromPath((String) source, params); else { String message = Logging.getMessage("generic.UnrecognizedSourceType", source); Logging.logger().severe(message); throw new IllegalArgumentException(message); } } catch (Exception e) { String message = Logging.getMessage("SHP.ExceptionAttemptingToReadShapefile", this.getValue(AVKey.DISPLAY_NAME)); Logging.logger().log(Level.SEVERE, message, e); throw new WWRuntimeException(message, e); } } /** * Opens an Shapefile from a general source. The source type may be one of the following:

    • {@link * java.io.InputStream}
    • {@link java.net.URL}
    • {@link File}
    • {@link String} containing a * valid URL description or a file or resource name available on the classpath.
    *

    * The source Shapefile may be accompanied by an optional index file, attribute file, and projection file. To be * recognized by this Shapefile, accompanying files must be in the same logical folder as the Shapefile, have the * same filename as the Shapefile, and have suffixes ".shx", ".dbf", and ".prj" respectively. If any of these files * do not exist, or cannot be read for any reason, the Shapefile opens without that information. *

    * This throws an exception if the shapefile's coordinate system is unsupported, or if the shapefile's coordinate * system is unsupported. * * @param source the source of the shapefile. * * @throws IllegalArgumentException if the source is null or an empty string. * @throws WWRuntimeException if the shapefile cannot be opened for any reason. */ public Shapefile(Object source) { this(source, null); } /** * Opens a Shapefile from an InputStream, and InputStreams to its optional resources. *

    * The source Shapefile may be accompanied optional streams to an index resource stream, an attribute resource * stream, and a projection resource stream. If any of these streams are null or cannot be read for any reason, the * Shapefile opens without that information. *

    * This throws an exception if the shapefile's coordinate system is unsupported. * * @param shpStream the shapefile geometry file stream. * @param shxStream the index file stream, can be null. * @param dbfStream the attribute file stream, can be null. * @param prjStream the projection file stream, can be null. * @param params parameter list describing metadata about the Shapefile, such as its map projection. * * @throws IllegalArgumentException if the shapefile geometry stream shpStream is null. * @throws WWRuntimeException if the shapefile cannot be opened for any reason, or if the shapefile's * coordinate system is unsupported. */ public Shapefile(InputStream shpStream, InputStream shxStream, InputStream dbfStream, InputStream prjStream, AVList params) { if (shpStream == null) { String message = Logging.getMessage("nullValue.InputStreamIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } try { this.setValue(AVKey.DISPLAY_NAME, shpStream.toString()); this.initializeFromStreams(shpStream, shxStream, dbfStream, prjStream, params); } catch (Exception e) { String message = Logging.getMessage("SHP.ExceptionAttemptingToReadShapefile", shpStream); Logging.logger().log(Level.SEVERE, message, e); throw new WWRuntimeException(message, e); } } /** * Opens a Shapefile from an InputStream, and InputStreams to its optional resources. *

    * The source Shapefile may be accompanied optional streams to an index resource stream, an attribute resource * stream, and a projection resource stream. If any of these streams are null or cannot be read for any reason, the * Shapefile opens without that information. *

    * This throws an exception if the shapefile's coordinate system is unsupported. * * @param shpStream the shapefile geometry file stream. * @param shxStream the index file stream, can be null. * @param dbfStream the attribute file stream, can be null. * @param prjStream the projection file stream, can be null. * * @throws IllegalArgumentException if the shapefile geometry stream shpStream is null. * @throws WWRuntimeException if the shapefile cannot be opened for any reason, or if the shapefile's * coordinate system is unsupported. */ public Shapefile(InputStream shpStream, InputStream shxStream, InputStream dbfStream, InputStream prjStream) { this(shpStream, shxStream, dbfStream, prjStream, null); } /** * Opens a Shapefile from an InputStream, and InputStreams to its optional resources. *

    * The source Shapefile may be accompanied optional streams to an index resource stream, and an attribute resource * stream. If any of these streams are null or cannot be read for any reason, the Shapefile opens without that * information. *

    * This throws an exception if the shapefile's coordinate system is unsupported. * * @param shpStream the shapefile geometry file stream. * @param shxStream the index file stream, can be null. * @param dbfStream the attribute file stream, can be null. * @param params parameter list describing metadata about the Shapefile, such as its map projection. * * @throws IllegalArgumentException if the shapefile geometry stream shpStream is null. * @throws WWRuntimeException if the shapefile cannot be opened for any reason, or if the shapefile's * coordinate system is unsupported. */ public Shapefile(InputStream shpStream, InputStream shxStream, InputStream dbfStream, AVList params) { this(shpStream, shxStream, dbfStream, null, params); } /** * Opens a Shapefile from an InputStream, and InputStreams to its optional resources. *

    * The source Shapefile may be accompanied optional streams to an index resource stream, and an attribute resource * stream. If any of these streams are null or cannot be read for any reason, the Shapefile opens without that * information. * * @param shpStream the shapefile geometry file stream. * @param shxStream the index file stream, can be null. * @param dbfStream the attribute file stream, can be null. * * @throws IllegalArgumentException if the shapefile geometry stream shpStream is null. * @throws WWRuntimeException if the shapefile cannot be opened for any reason. */ public Shapefile(InputStream shpStream, InputStream shxStream, InputStream dbfStream) { this(shpStream, shxStream, dbfStream, null, null); } /** * Returns the shapefile's version field, or -1 if the Shapefile failed to open. * * @return the shapefile's version field, or -1 to denote the Shapefile failed to open. */ public int getVersion() { return this.header != null ? this.header.version : -1; } /** * Returns the raw shapefile's length, or -1 if the Shapefile failed to open. * * @return the raw shapefile's length in bytes, or -1 to denote the Shapefile failed to open. */ public int getLength() { return this.header != null ? this.header.fileLength : -1; } /** * Returns the shapefile's shape type: null if the Shapefile failed to open, otherwise one of the following symbolic * constants:

    • {@link #SHAPE_NULL}
    • {@link #SHAPE_POINT}
    • {@link #SHAPE_MULTI_POINT}
    • *
    • {@link #SHAPE_POLYLINE}
    • {@link #SHAPE_POLYGON}
    • {@link #SHAPE_POINT_M}
    • {@link * #SHAPE_MULTI_POINT_M}
    • {@link #SHAPE_POLYLINE_M}
    • {@link #SHAPE_POLYGON_M}
    • {@link * #SHAPE_POINT_Z}
    • {@link #SHAPE_MULTI_POINT_Z}
    • {@link #SHAPE_POLYLINE_Z}
    • {@link * #SHAPE_POLYGON_Z}
    • {@link #SHAPE_MULTI_PATCH}
    * * @return the shapefile's shape type: null if the Shapefile failed to open, othersise a symbolic constants denoting * the type. */ public String getShapeType() { return this.header != null ? this.header.shapeType : null; } /** * Returns a four-element array containing the shapefile's bounding rectangle, or null if the Shapefile failed to * open. The returned array is ordered as follows: minimum Y, maximum Y, minimum X, and maximum X. If the * Shapefile's coordinate system is geographic, the elements can be interpreted as angular degrees in the order * minimum latitude, maximum latitude, minimum longitude, and maximum longitude. * * @return the shapefile's bounding rectangle, or null to denote the Shapefile failed to open. */ public double[] getBoundingRectangle() { return this.header != null ? this.header.boundingRectangle : null; } /** * Returns the number of records in the shapefile, or -1 if the number if records is unknown. * * @return the number of records in the shapefile, or -1 to denote an unknown number of records. */ public int getNumberOfRecords() { return this.index != null ? this.index.length / 2 : -1; } /** * Get the underlying {@link CompoundVecBuffer} describing the shapefile's points. * * @return the underlying {@link CompoundVecBuffer}. */ public CompoundVecBuffer getPointBuffer() { return this.pointBuffer; } /** * Returns true if the Shapefile has a more records, and false if all records have been * read. * * @return true if the Shapefile has a more records; false otherwise. */ public boolean hasNext() { if (!this.open || this.header == null) return false; int contentLength = this.header.fileLength - HEADER_LENGTH; return this.numBytesRead < contentLength; } /** * Reads the Shapefile's next record and returns the result as a new {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord}. * The record's type depends on the Shapefile's type, and is one of the following:
    • {@link * gov.nasa.worldwind.formats.shapefile.ShapefileRecordPoint} if type is {@link #SHAPE_POINT}, {@link * #SHAPE_POINT_M} or {@link #SHAPE_POINT_Z}.
    • {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecordMultiPoint} * if type is {@link #SHAPE_MULTI_POINT}, {@link #SHAPE_MULTI_POINT_M} or {@link #SHAPE_MULTI_POINT_Z}.
    • *
    • {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecordPolyline} if type is {@link #SHAPE_POLYLINE}, * {@link #SHAPE_POLYLINE_M} or {@link #SHAPE_POLYLINE_Z}.
    • {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecordPolygon} * if type is {@link #SHAPE_POLYGON}, {@link #SHAPE_POLYGON_M} or {@link #SHAPE_POLYGON_Z}.
    *

    * This throws an exception if the JVM cannot allocate enough memory to hold the buffer used to store the record's * point coordinates. * * @return the Shapefile's next record. * * @throws IllegalStateException if the Shapefile is closed or if the Shapefile has no more records. * @throws WWRuntimeException if an exception occurs while reading the record. * @see #getShapeType() */ public ShapefileRecord nextRecord() { if (!this.open) { String message = Logging.getMessage("SHP.ShapefileClosed", this.getValue(AVKey.DISPLAY_NAME)); Logging.logger().severe(message); throw new IllegalStateException(message); } if (this.header == null) // This should never happen, but we check anyway. { String message = Logging.getMessage("SHP.HeaderIsNull", this.getValue(AVKey.DISPLAY_NAME)); Logging.logger().severe(message); throw new IllegalStateException(message); } int contentLength = this.header.fileLength - HEADER_LENGTH; if (contentLength <= 0 || this.numBytesRead >= contentLength) { String message = Logging.getMessage("SHP.NoRecords", this.getValue(AVKey.DISPLAY_NAME)); Logging.logger().severe(message); throw new IllegalStateException(message); } ShapefileRecord record; try { record = this.readNextRecord(); } catch (Exception e) { String message = Logging.getMessage("SHP.ExceptionAttemptingToReadShapefileRecord", this.getValue(AVKey.DISPLAY_NAME)); Logging.logger().log(Level.SEVERE, message, e); throw new WWRuntimeException(message, e); } this.numRecordsRead++; return record; } /** * Closes the Shapefile, freeing any resources allocated during reading except the buffer containing the Shapefile's * points. This closes any {@link java.io.InputStream} passed to the Shapefile during construction. Subsequent calls * to {@link #nextRecord()} cause an IllegalStateException. *

    * After closing, the Shapefile's header information and point coordinates are still available. The following * methods are safe to call:

    • {@link #getVersion()}
    • {@link #getLength()}
    • {@link * #getShapeType()}
    • {@link #getBoundingRectangle()}
    • {@link #getNumberOfRecords()}
    • {@link * #getPointBuffer()}
    */ public void close() { if (this.shpChannel != null) { WWIO.closeStream(this.shpChannel, null); this.shpChannel = null; } if (this.shxChannel != null) { WWIO.closeStream(this.shxChannel, null); this.shxChannel = null; } if (this.prjChannel != null) { WWIO.closeStream(this.prjChannel, null); this.prjChannel = null; } if (this.attributeFile != null) { this.attributeFile.close(); this.attributeFile = null; } this.recordHeaderBuffer = null; this.recordContentBuffer = null; this.mappedShpBuffer = null; this.open = false; } /** * Returns whether the shapefile's point coordinates should be normalized. * * @return true if the shapefile's points should be normalized; false otherwise. */ protected boolean isNormalizePoints() { return this.normalizePoints; } /** * Specifies if the shapefile's point coordinates should be normalized. Defaults to false. * * @param normalizePoints true if the shapefile's points should be normalized; false * otherwise. */ protected void setNormalizePoints(boolean normalizePoints) { this.normalizePoints = normalizePoints; } //**************************************************************// //******************** Initialization ************************// //**************************************************************// protected void initializeFromFile(File file, AVList params) throws IOException { if (!file.exists()) { String message = Logging.getMessage("generic.FileNotFound", file.getPath()); Logging.logger().severe(message); throw new FileNotFoundException(message); } // Attempt to map the Shapefile into system memory in copy-on-write mode. We open in copy-on-write mode so that // the Shapefile reader and the application can change a record's point data without affecting the original // file. Although we never change the file's bytes on disk, the file must be accessible for reading and writing // to use copy-on-write mode. Therefore files locked for writing and files stored on a read-only device // (e.g. CD, DVD) cannot be memory mapped. if (file.canRead() && file.canWrite()) { try { // Memory map the Shapefile in copy-on-write mode. this.mappedShpBuffer = WWIO.mapFile(file, FileChannel.MapMode.PRIVATE); Logging.logger().finer(Logging.getMessage("SHP.MemoryMappingEnabled", file.getPath())); } catch (IOException e) { Logging.logger().log(Level.WARNING, Logging.getMessage("SHP.ExceptionAttemptingToMemoryMap", file.getPath()), e); } } // If attempting to memory map the Shapefile failed, fall back on opening the file as a generic stream. Throw an // IOException if the file cannot be opened via stream. if (this.mappedShpBuffer == null) this.shpChannel = Channels.newChannel(new BufferedInputStream(new FileInputStream(file))); // Attempt to open the optional index and projection files associated with the Shapefile. Ignore exceptions // thrown while attempting to open these optional resource streams. We wrap each source InputStream in a // BufferedInputStream because this increases read performance, even when the stream is wrapped in an NIO // Channel. InputStream shxStream = this.getFileStream(WWIO.replaceSuffix(file.getPath(), INDEX_FILE_SUFFIX)); if (shxStream != null) this.shxChannel = Channels.newChannel(WWIO.getBufferedInputStream(shxStream)); InputStream prjStream = this.getFileStream(WWIO.replaceSuffix(file.getPath(), PROJECTION_FILE_SUFFIX)); if (prjStream != null) this.prjChannel = Channels.newChannel(WWIO.getBufferedInputStream(prjStream)); // Initialize the Shapefile before opening its associated attributes file. This avoids opening the attributes // file if an exception is thrown while opening the Shapefile. this.setValue(AVKey.DISPLAY_NAME, file.getPath()); this.initialize(params); // Open the shapefile attribute source as a DBaseFile. We let the DBaseFile determine how to handle source File. File dbfFile = new File(WWIO.replaceSuffix(file.getPath(), ATTRIBUTE_FILE_SUFFIX)); if (dbfFile.exists()) { try { this.attributeFile = new DBaseFile(dbfFile); } catch (Exception e) { // Exception already logged by DBaseFile constructor. } } } protected void initializeFromURL(URL url, AVList params) throws IOException { // Opening the Shapefile URL as a URL connection. Throw an IOException if the URL connection cannot be opened, // or if it's an invalid Shapefile connection. URLConnection connection = url.openConnection(); String message = this.validateURLConnection(connection, SHAPE_CONTENT_TYPES); if (message != null) { throw new IOException(message); } this.shpChannel = Channels.newChannel(WWIO.getBufferedInputStream(connection.getInputStream())); // Attempt to open the optional index and projection resources associated with the Shapefile. Ignore exceptions // thrown while attempting to open these optional resource streams, but log a warning if the URL connection is // invalid. We wrap each source InputStream in a BufferedInputStream because this increases read performance, // even when the stream is wrapped in an NIO Channel. URLConnection shxConnection = this.getURLConnection(WWIO.replaceSuffix(url.toString(), INDEX_FILE_SUFFIX)); if (shxConnection != null) { message = this.validateURLConnection(shxConnection, INDEX_CONTENT_TYPES); if (message != null) Logging.logger().warning(message); else { InputStream shxStream = this.getURLStream(shxConnection); if (shxStream != null) this.shxChannel = Channels.newChannel(WWIO.getBufferedInputStream(shxStream)); } } URLConnection prjConnection = this.getURLConnection(WWIO.replaceSuffix(url.toString(), PROJECTION_FILE_SUFFIX)); if (prjConnection != null) { message = this.validateURLConnection(prjConnection, PROJECTION_CONTENT_TYPES); if (message != null) Logging.logger().warning(message); else { InputStream prjStream = this.getURLStream(prjConnection); if (prjStream != null) this.prjChannel = Channels.newChannel(WWIO.getBufferedInputStream(prjStream)); } } // Initialize the Shapefile before opening its associated attributes file. This avoids opening the attributes // file if an exception is thrown while opening the Shapefile. this.setValue(AVKey.DISPLAY_NAME, url.toString()); this.initialize(params); // Open the shapefile attribute source as a DBaseFile. We let the DBaseFile determine how to handle source URL. URL dbfURL = WWIO.makeURL(WWIO.replaceSuffix(url.toString(), ATTRIBUTE_FILE_SUFFIX)); if (dbfURL != null) { try { this.attributeFile = new DBaseFile(dbfURL); } catch (Exception e) { // Exception already logged by DBaseFile constructor. } } } protected void initializeFromStreams(InputStream shpStream, InputStream shxStream, InputStream dbfStream, InputStream prjStream, AVList params) throws IOException { // Create Channels for the collection of resources used by the Shapefile reader. We wrap each source InputStream // in a BufferedInputStream because this increases read performance, even when the stream is wrapped in an NIO // Channel. if (shpStream != null) this.shpChannel = Channels.newChannel(WWIO.getBufferedInputStream(shpStream)); if (shxStream != null) this.shxChannel = Channels.newChannel(WWIO.getBufferedInputStream(shxStream)); if (prjStream != null) this.prjChannel = Channels.newChannel(WWIO.getBufferedInputStream(prjStream)); // Initialize the Shapefile before opening its associated attributes file. This avoids opening the attributes // file if an exception is thrown while opening the Shapefile. this.initialize(params); // Open the shapefile attribute source as a DBaseFile. We let the DBaseFile determine how to handle its source // InputStream. if (dbfStream != null) { try { this.attributeFile = new DBaseFile(dbfStream); } catch (Exception e) { // Exception already logged by DBaseFile constructor. } } } protected void initializeFromPath(String path, AVList params) throws IOException { File file = new File(path); if (file.exists()) { this.initializeFromFile(file, params); return; } URL url = WWIO.makeURL(path); if (url != null) { this.initializeFromURL(url, params); return; } String message = Logging.getMessage("generic.UnrecognizedSourceType", path); Logging.logger().severe(message); throw new IllegalArgumentException(message); } /** * Prepares the Shapefile for reading. This reads the Shapefile's accompanying index and projection (if they're * available), validates the Shapefile's coordinate system, and reads the Shapefile's header. * * @param params arameter list describing metadata about the Shapefile, such as its map projection, or null to * specify no additional parameters. * * @throws IOException if an error occurs while reading the Shapefile's header. */ protected void initialize(AVList params) throws IOException { // Attempt to read this Shapefile's projection resource, and set any projection parameters parsed from that // resource. If reading the projection resource fails, log the exception and continue. try { AVList csParams = this.readCoordinateSystem(); if (csParams != null) this.setValues(csParams); } catch (IOException e) { Logging.logger().log(Level.WARNING, Logging.getMessage("SHP.ExceptionAttemptingToReadProjection", this.getValue(AVKey.DISPLAY_NAME)), e); } // Set the Shapefile's caller specified parameters. We do this after reading the projection parameters to give // the caller's parameters priority over the projection parameters. if (params != null) { this.setValues(params); } // Validate the projection projection parameters specified in either the Shapefile's accompanying projection // file or the caller specified parameters. If the projection is unspecified the Shapefile makes no assumptions // about what the coordinates represent. Throw a WWRuntimeException if the projection is either invalid or // unsupported. Subsequent attempts to query the Shapefile's header data or read its records cause an exception. String message = this.validateCoordinateSystem(this); if (message != null) { throw new WWRuntimeException(message); } // Attempt to read this Shapefile's index resource. If reading the index resource fails, log the exception and // continue. We read the index after reading any projection information and assigning the caller specified // parameters. This ensures that any coordinates in the header are converted according to the Shapefile's // coordinate system. try { this.index = this.readIndex(); } catch (IOException e) { Logging.logger().log(Level.WARNING, Logging.getMessage("SHP.ExceptionAttemptingToReadIndex", this.getValue(AVKey.DISPLAY_NAME)), e); } // Read this Shapefile's header and flag the Shapefile as open. We read the header after reading any projection // information and assigning the caller specified parameters. This ensures that any coordinates in the header // are converted according to the Shapefile's coordinate system. this.header = this.readHeader(); this.open = true; // Specify that the record's points should be normalized if the header is flagged as needing normalized points. this.setNormalizePoints(this.header.normalizePoints); } protected InputStream getFileStream(String path) { try { return new FileInputStream(path); } catch (Exception e) { return null; } } protected URLConnection getURLConnection(String urlString) { try { URL url = new URL(urlString); return url.openConnection(); } catch (Exception e) { return null; } } protected InputStream getURLStream(URLConnection connection) { try { return connection.getInputStream(); } catch (Exception e) { return null; } } protected String validateURLConnection(URLConnection connection, String[] acceptedContentTypes) { try { if (connection instanceof HttpURLConnection && ((HttpURLConnection) connection).getResponseCode() != HttpURLConnection.HTTP_OK) { return Logging.getMessage("HTTP.ResponseCode", ((HttpURLConnection) connection).getResponseCode(), connection.getURL()); } } catch (Exception e) { return Logging.getMessage("URLRetriever.ErrorOpeningConnection", connection.getURL()); } String contentType = connection.getContentType(); if (WWUtil.isEmpty(contentType)) return null; for (String type : acceptedContentTypes) { if (contentType.trim().toLowerCase().startsWith(type)) return null; } // Return an exception if the content type does not match the expected type. return Logging.getMessage("HTTP.UnexpectedContentType", contentType, Arrays.toString(acceptedContentTypes)); } //**************************************************************// //******************** Header ********************************// //**************************************************************// /** * Reads the {@link Header} from this Shapefile. This file is assumed to have a header. * * @return a {@link Header} instance. * * @throws IOException if the header cannot be read for any reason. */ protected Header readHeader() throws IOException { ByteBuffer buffer; if (this.mappedShpBuffer != null) { buffer = this.mappedShpBuffer; } else { buffer = ByteBuffer.allocate(HEADER_LENGTH); WWIO.readChannelToBuffer(this.shpChannel, buffer); } if (buffer.remaining() < HEADER_LENGTH) { // Let the caller catch and log the message. throw new WWRuntimeException(Logging.getMessage("generic.InvalidFileLength", buffer.remaining())); } return this.readHeaderFromBuffer(buffer); } /** * Reads a {@link Header} instance from the given {@link java.nio.ByteBuffer}; *

    * The buffer current position is assumed to be set at the start of the header and will be set to the end of the * header after this method has completed. * * @param buffer the Header @link java.nio.ByteBuffer} to read from. * * @return a {@link Header} instances. * * @throws IOException if the header cannot be read for any reason. */ protected Header readHeaderFromBuffer(ByteBuffer buffer) throws IOException { Header header = null; // Save the buffer's current position. int pos = buffer.position(); try { // Read file code - first 4 bytes, big endian buffer.order(ByteOrder.BIG_ENDIAN); int fileCode = buffer.getInt(); if (fileCode != FILE_CODE) { // Let the caller catch and log the message. throw new WWUnrecognizedException(Logging.getMessage("SHP.UnrecognizedShapefile", fileCode)); } // Skip 5 unused ints buffer.position(buffer.position() + 5 * 4); // File length int lengthInWords = buffer.getInt(); // Switch to little endian for the remaining part buffer.order(ByteOrder.LITTLE_ENDIAN); // Read remaining header data int version = buffer.getInt(); int type = buffer.getInt(); BoundingRectangle rect = this.readBoundingRectangle(buffer); // Check whether the shape type is supported String shapeType = getShapeType(type); if (shapeType == null) { // Let the caller catch and log the message. throw new WWRuntimeException(Logging.getMessage("SHP.UnsupportedShapeType", type)); } // Assemble header header = new Header(); header.fileLength = lengthInWords * 2; // one word = 2 bytes header.version = version; header.shapeType = shapeType; header.boundingRectangle = rect.coords; header.normalizePoints = rect.isNormalized; } finally { // Move to the end of the header. buffer.position(pos + HEADER_LENGTH); } return header; } //**************************************************************// //******************** Index *********************************// //**************************************************************// /** * Reads the Shapefile's accompanying index file and return the indices as an array of integers. Each array element * represents the byte offset of the i'th record from the start of the Shapefile. This returns null if * this Shapefile has no accompanying index file, if the index file is empty, or if the JVM cannot allocate enough * memory to hold the index. * * @return the Shapefile's record offset index, or null if the Shapefile has no accompanying index * file, if the index file is empty, or if the index cannot be allocated. * * @throws IOException if an exception occurs during reading. */ protected int[] readIndex() throws IOException { // The Shapefile index resource is optional. Return null if we don't have a stream to an index resource. if (this.shxChannel == null) return null; ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH); WWIO.readChannelToBuffer(this.shxChannel, buffer); // Return null if the index is empty or is smaller than the minimum required size. if (buffer.remaining() < HEADER_LENGTH) return null; Header indexHeader = this.readHeaderFromBuffer(buffer); int numRecords = (indexHeader.fileLength - HEADER_LENGTH) / 8; int numElements = 2 * numRecords; // 2 elements per record: offset and length. int indexLength = 8 * numRecords; // 8 bytes per record. int[] array; try { buffer = ByteBuffer.allocate(indexLength); array = new int[numElements]; } catch (OutOfMemoryError e) { // Log a warning that we could not allocate enough memory to hold the Shapefile index. Shapefile parsing // can continue without the optional index, so we catch the exception and return immediately. Logging.logger().log(Level.WARNING, Logging.getMessage("SHP.OutOfMemoryAllocatingIndex", this.getValue(AVKey.DISPLAY_NAME)), e); return null; } buffer.order(ByteOrder.BIG_ENDIAN); WWIO.readChannelToBuffer(this.shxChannel, buffer); buffer.asIntBuffer().get(array); for (int i = 0; i < numElements; i++) { array[i] *= 2; // Convert indices from 16-bit words to byte indices. } return array; } //**************************************************************// //******************** Coordinate System *********************// //**************************************************************// /** * Reads the Shapefile's accompanying projection file as an OGC coordinate system encoded in well-known text format, * and returns the coordinate system parameters. This returns null if this Shapefile has no * accompanying projection file or if the projection file is empty. For details, see the OGC Coordinate Transform * Service (CT) specification at http://www.opengeospatial.org/standards/ct. * * @return coordinate system parameters parsed from the projection file, or null if this Shapefile has * no accompanying projection file or the projection file is empty. * * @throws IOException if an exception occurs during reading. */ protected AVList readCoordinateSystem() throws IOException { // The Shapefile projection resource is optional. Return the parameter list unchanged if we don't have a stream // to a projection resource. if (this.prjChannel == null) return null; // Read the Shapefile's associated projection to a String, using the default character encoding. Decode the // projection text as an OGC coordinate system formatted as well-known text. String text = WWIO.readChannelToString(this.prjChannel, null); // Return null if the projection file is empty. if (WWUtil.isEmpty(text)) return null; return WorldFile.decodeOGCCoordinateSystemWKT(text, null); } /** * Returns a string indicating an error with the Shapefile's coordinate system parameters, or null to indicate that * the coordinate parameters are valid. * * @param params the Shapefile's coordinate system parameters. * * @return a non-empty string if the coordinate system parameters are invalid; null otherwise. */ protected String validateCoordinateSystem(AVList params) { Object o = params.getValue(AVKey.COORDINATE_SYSTEM); if (!this.hasKey(AVKey.COORDINATE_SYSTEM)) { Logging.logger().warning( Logging.getMessage("generic.UnspecifiedCoordinateSystem", this.getValue(AVKey.DISPLAY_NAME))); return null; } else if (AVKey.COORDINATE_SYSTEM_GEOGRAPHIC.equals(o)) { return null; } else if (AVKey.COORDINATE_SYSTEM_PROJECTED.equals(o)) { return this.validateProjection(params); } else { return Logging.getMessage("generic.UnsupportedCoordinateSystem", o); } } /** * Returns a string indicating an error with the Shapefile's projection parameters, or null to indicate that the * projection parameters are valid. * * @param params the Shapefile's projection parameters. * * @return a non-empty string if the projection parameters are invalid; null otherwise. */ protected String validateProjection(AVList params) { Object proj = params.getValue(AVKey.PROJECTION_NAME); if (AVKey.PROJECTION_UTM.equals(proj)) { StringBuilder sb = new StringBuilder(); // Validate the UTM zone. Object o = params.getValue(AVKey.PROJECTION_ZONE); if (o == null) sb.append(Logging.getMessage("generic.ZoneIsMissing")); else if (!(o instanceof Integer) || ((Integer) o) < 1 || ((Integer) o) > 60) sb.append(Logging.getMessage("generic.ZoneIsInvalid", o)); // Validate the UTM hemisphere. o = params.getValue(AVKey.PROJECTION_HEMISPHERE); if (o == null) sb.append(sb.length() > 0 ? ", " : "").append(Logging.getMessage("generic.HemisphereIsMissing")); else if (!o.equals(AVKey.NORTH) && !o.equals(AVKey.SOUTH)) sb.append(sb.length() > 0 ? ", " : "").append(Logging.getMessage("generic.HemisphereIsInvalid", o)); return sb.length() > 0 ? sb.toString() : null; } else { return Logging.getMessage("generic.UnsupportedProjection", proj); } } //**************************************************************// //******************** Shape Records *************************// //**************************************************************// /** * Reads the next {@link ShapefileRecord} instance from this Shapefile. This file is assumed to have one or more * remaining records available. * * @return a {@link ShapefileRecord} instance. * * @throws IOException if the record cannot be read for any reason. */ protected ShapefileRecord readNextRecord() throws IOException { ByteBuffer buffer; if (this.mappedShpBuffer != null) { // Save the mapped buffer's current position and limit. int pos = this.mappedShpBuffer.position(); // Read the record number and the content length from the record header. this.mappedShpBuffer.order(ByteOrder.BIG_ENDIAN); //int recordNumber = this.shpMappedBuffer.getInt(pos); int contentLength = this.mappedShpBuffer.getInt(pos + 4) * 2; int recordLength = ShapefileRecord.RECORD_HEADER_LENGTH + contentLength; // Position the mapped buffer at the beginning of the record, and set the mapped buffer's limit to the end // of the record. this.mappedShpBuffer.position(pos); this.mappedShpBuffer.limit(pos + recordLength); this.numBytesRead += recordLength; buffer = this.mappedShpBuffer; } else { // Allocate a buffer to hold the record header. if (this.recordHeaderBuffer == null) this.recordHeaderBuffer = ByteBuffer.allocate(ShapefileRecord.RECORD_HEADER_LENGTH); // Read the header bytes. this.recordHeaderBuffer.clear(); this.recordHeaderBuffer.order(ByteOrder.BIG_ENDIAN); WWIO.readChannelToBuffer(this.shpChannel, this.recordHeaderBuffer); // Read the record number and the content length. //int recordNumber = this.recordHeaderBuffer.getInt(0); int contentLength = this.recordHeaderBuffer.getInt(4) * 2; int recordLength = ShapefileRecord.RECORD_HEADER_LENGTH + contentLength; // Allocate a buffer to hold the record content. if (this.recordContentBuffer == null || this.recordContentBuffer.capacity() < recordLength) this.recordContentBuffer = ByteBuffer.allocate(recordLength); this.recordContentBuffer.limit(recordLength); this.recordContentBuffer.rewind(); // Put the record header in the record buffer, and read the remaining record content. this.recordContentBuffer.put(this.recordHeaderBuffer); WWIO.readChannelToBuffer(this.shpChannel, this.recordContentBuffer); this.numBytesRead += recordLength; buffer = this.recordContentBuffer; } ShapefileRecord record; try { record = this.readRecordFromBuffer(buffer); } finally { // Restore the mapped buffer's limit to its capacity. if (this.mappedShpBuffer != null) this.mappedShpBuffer.limit(this.mappedShpBuffer.capacity()); } return record; } /** * Reads a {@link ShapefileRecord} instance from the given {@link java.nio.ByteBuffer}, or null if the buffer * contains a null record. *

    * 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. * * @param buffer the shapefile record {@link java.nio.ByteBuffer} to read from. * * @return a {@link ShapefileRecord} instance. */ protected ShapefileRecord readRecordFromBuffer(ByteBuffer buffer) { ShapefileRecord record = this.createRecord(buffer); if (record != null) { // Read the record's attribute data. if (this.attributeFile != null && this.attributeFile.hasNext()) { record.setAttributes(this.attributeFile.nextRecord()); } } return record; } /** * Returns a new {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord} from the specified * buffer. The buffer's 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. *

    * This returns an instance of of ShapefileRecord appropriate for the record's shape type. For example, if the * record's shape type is SHAPE_POINT, this returns a ShapefileRecordPoint, and if the * record's shape type is SHAPE_NULL, this returns ShapefileRecordNull. *

    * This returns null if the record's shape type is not one of the following types: * SHAPE_POINT, SHAPE_POINT_M, SHAPE_POINT_Z, SHAPE_MULTI_POINT, * SHAPE_MULTI_POINT_M, SHAPE_MULTI_POINT_Z, SHAPE_NULL, * SHAPE_POLYGON, SHAPE_POLYGON_M, SHAPE_POLYGON_Z, * SHAPE_POLYLINE, SHAPE_POLYLINE_M, SHAPE_POLYLINE_Z. * * @param buffer the buffer containing the record's content. * * @return a new {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord} instance, null if the * record's shape type is not one of the recognized types. */ protected ShapefileRecord createRecord(ByteBuffer buffer) { String shapeType = this.readRecordShapeType(buffer); // Select proper record class if (isPointType(shapeType)) { return this.createPoint(buffer); } else if (isMultiPointType(shapeType)) { return this.createMultiPoint(buffer); } else if (isPolylineType(shapeType)) { return this.createPolyline(buffer); } else if (isPolygonType(shapeType)) { return this.createPolygon(buffer); } else if (isNullType(shapeType)) { return this.createNull(buffer); } return null; } /** * Returns a new "null" {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord} from the specified 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. * * @param buffer the buffer containing the point record's content. * * @return a new point {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord}. */ protected ShapefileRecord createNull(ByteBuffer buffer) { return new ShapefileRecordNull(this, buffer); } /** * Returns a new point {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord} from the specified 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. * * @param buffer the buffer containing the point record's content. * * @return a new point {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord}. */ protected ShapefileRecord createPoint(ByteBuffer buffer) { return new ShapefileRecordPoint(this, buffer); } /** * Returns a new multi-point {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord} from the specified * 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. * * @param buffer the buffer containing the multi-point record's content. * * @return a new point {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord}. */ protected ShapefileRecord createMultiPoint(ByteBuffer buffer) { return new ShapefileRecordMultiPoint(this, buffer); } /** * Returns a new polyline {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord} from the specified 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. * * @param buffer the buffer containing the polyline record's content. * * @return a new point {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord}. */ protected ShapefileRecord createPolyline(ByteBuffer buffer) { return new ShapefileRecordPolyline(this, buffer); } /** * Returns a new polygon {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord} from the specified 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. * * @param buffer the buffer containing the polygon record's content. * * @return a new point {@link gov.nasa.worldwind.formats.shapefile.ShapefileRecord}. */ protected ShapefileRecord createPolygon(ByteBuffer buffer) { return new ShapefileRecordPolygon(this, buffer); } /** * Read and return a record's shape type from a record buffer. * * @param buffer the record buffer to read from. * * @return the record's shape type. */ protected String readRecordShapeType(ByteBuffer buffer) { // Read shape type - little endian buffer.order(ByteOrder.LITTLE_ENDIAN); int type = buffer.getInt(buffer.position() + 2 * 4); // skip record number and length as ints String shapeType = this.getShapeType(type); if (shapeType == null) { // Let the caller catch and log the exception. throw new WWRuntimeException(Logging.getMessage("SHP.UnsupportedShapeType", type)); } return shapeType; } /** * Maps the integer shape type from the shapefile to the corresponding shape type defined above. * * @param type the integer shape type. * * @return the mapped shape type. */ protected String getShapeType(int type) { // Cases commented out indicate shape types not implemented switch (type) { case 0: return SHAPE_NULL; case 1: return SHAPE_POINT; case 3: return SHAPE_POLYLINE; case 5: return SHAPE_POLYGON; case 8: return SHAPE_MULTI_POINT; case 11: return SHAPE_POINT_Z; case 13: return SHAPE_POLYLINE_Z; case 15: return SHAPE_POLYGON_Z; case 18: return SHAPE_MULTI_POINT_Z; case 21: return SHAPE_POINT_M; case 23: return SHAPE_POLYLINE_M; case 25: return SHAPE_POLYGON_M; case 28: return SHAPE_MULTI_POINT_M; // case 31: // return SHAPE_MULTI_PATCH; default: return null; // unsupported shape type } } //**************************************************************// //******************** Point Data ****************************// //**************************************************************// /** * Add point coordinates to the Shapefile starting at the buffer's positions and ending at the specified number of * points, and returns an address to the point coordinates in the Shapefile's backing point buffer. Points are read * as (X,Y) pairs of 64-bit floating point numbers. This throws an exception if the JVM cannot allocate enough * memory to hold the Shapefile's backing point buffer. * * @param record the record associated with the point coordinates, may be null. * @param buffer the buffer to read points from. * @param numPoints the number of (X,Y) pairs to read. * * @return the point's address in the Shapefile's backing point buffer. */ protected int addPoints(ShapefileRecord record, ByteBuffer buffer, int numPoints) { DoubleBuffer pointBuffer; // Read the point data, keeping track of the start and end of the point data. int pos = buffer.position(); int limit = buffer.position() + 2 * WWBufferUtil.SIZEOF_DOUBLE * numPoints; try { // Set the buffer's limit to include the number of bytes required to hold 2 double precision values for each // point, then read the point data between the buffer's current position and limit. buffer.limit(limit); pointBuffer = this.readPoints(record, buffer); } finally { // Restore the buffer's limit to its original value, and set its position at the end of the point data. buffer.clear(); buffer.position(limit); } // Add the point data to the Shapefile's internal point buffer. if (this.mappedShpBuffer != null) { if (this.pointBuffer == null) { // Create a VecBufferBlocks to hold this Shapefile's point data. Shapefile points are 2-tuples stored in // IEEE 64-bit floating point format, in little endian byte order. ByteBuffer buf = this.mappedShpBuffer.duplicate(); buf.order(ByteOrder.LITTLE_ENDIAN); buf.clear(); this.pointBuffer = new VecBufferBlocks(2, AVKey.FLOAT64, buf); } // Add the point's byte range to the VecBufferBlocks. return ((VecBufferBlocks) this.pointBuffer).addBlock(pos, limit - 1); } else { if (this.pointBuffer == null) { // Create a CompoundVecBuffer to hold this Shapefile's point data. int totalPointsEstimate = this.computeNumberOfPointsEstimate(); DoubleBuffer doubleBuffer; try { doubleBuffer = Buffers.newDirectDoubleBuffer(2 * totalPointsEstimate); } catch (OutOfMemoryError e) { // Let the caller catch and log the exception. If we cannot allocate enough memory to hold the // point buffer, we throw an exception indicating that the read operation should be terminated. throw new WWRuntimeException(Logging.getMessage("SHP.OutOfMemoryAllocatingPointBuffer", this.getValue(AVKey.DISPLAY_NAME)), e); } this.pointBuffer = new VecBufferSequence( new VecBuffer(2, new BufferWrapper.DoubleBufferWrapper(doubleBuffer))); } // Append the point coordinates to the VecBufferSequence. VecBuffer vecBuffer = new VecBuffer(2, new BufferWrapper.DoubleBufferWrapper(pointBuffer)); return ((VecBufferSequence) this.pointBuffer).append(vecBuffer); } } /** * Estimate the number of points in a shapefile. * * @return a liberal estimate of the number of points in the shapefile. */ @SuppressWarnings({"StringEquality"}) protected int computeNumberOfPointsEstimate() { // Compute the header overhead, subtract it from the file size, then divide by point size to get the estimate. // The Parts array is not included in the overhead, so the estimate will be slightly greater than the number of // points needed if the shape is a type with a Parts array. Measure values and ranges are also not included in // the estimate because they are optional, so if they are included the estimate will also be greater than // necessary. final int numRecords = this.getNumberOfRecords(); // Return very liberal estimate based on file size if num records unknown. if (numRecords < 0) return (this.getLength() - HEADER_LENGTH) / 16; // num X, Y tuples that can fit in the file length int overhead = HEADER_LENGTH + numRecords * 12; //12 bytes per record for record header and record shape type String shapeType = this.getShapeType(); if (shapeType == SHAPE_POINT || shapeType == SHAPE_POINT_M) return (this.getLength() - overhead) / 16; // 16 = two doubles, X and Y if (shapeType == SHAPE_MULTI_POINT || shapeType == SHAPE_MULTI_POINT_M) // Add 32 bytes per record for bounding box + 4 bytes for one int per record return (this.getLength() - (overhead + numRecords * (32 + 4))) / 16; // 16 = two doubles, X and Y if (shapeType == SHAPE_POLYLINE || shapeType == SHAPE_POLYGON || shapeType == SHAPE_POLYLINE_M || shapeType == SHAPE_POLYGON_M) // Add 32 bytes per record for bounding box + 8 bytes for two ints per record return (this.getLength() - (overhead + numRecords * (32 + 8))) / 16; // 16 = two doubles, X and Y if (shapeType == SHAPE_POINT_Z) return (this.getLength() - overhead) / 24; // 24 = three doubles, X, Y, Z if (shapeType == SHAPE_MULTI_POINT_Z) // Add 48 bytes per record for bounding box + 4 bytes for one int per record return (this.getLength() - (overhead + numRecords * (48 + 4))) / 24; // 24 = three doubles, X, Y, Z if (shapeType == SHAPE_POLYLINE_Z || shapeType == SHAPE_POLYGON_Z) // Add 48 bytes per record for bounding box + 8 bytes for two ints per record return (this.getLength() - (overhead + numRecords * (48 + 8))) / 24; // 24 = three doubles, X, Y and Z // The shape type should have been checked before calling this method, so we shouldn't reach this code. // Let the caller catch and log the exception. throw new WWRuntimeException(Logging.getMessage("SHP.UnsupportedShapeType", shapeType)); } /** * Returns a {@link java.nio.DoubleBuffer} containing the (X,Y) tuples between the buffer's position and its limit. * This returns null if the buffer is null or if the buffer has no remaining elements. The returned coordinates are * interpreted according to the Shapefile's coordinate system. This throws a {@link * gov.nasa.worldwind.exception.WWRuntimeException} if the coordinate system is unsupported. *

    * The buffer current position is assumed to be set at the start of the point data and will be set to the end of the * point data after this method has completed. * * @param record the record associated with the point coordinates, may be null. * @param buffer the buffer to read point coordinates from. * * @return a buffer containing the point coordinates. * * @throws WWRuntimeException if the Shapefile's coordinate system is unsupported. */ protected DoubleBuffer readPoints(ShapefileRecord record, ByteBuffer buffer) { if (buffer == null || !buffer.hasRemaining()) return null; Object o = this.getValue(AVKey.COORDINATE_SYSTEM); if (!this.hasKey(AVKey.COORDINATE_SYSTEM)) return this.readUnspecifiedPoints(record, buffer); else if (AVKey.COORDINATE_SYSTEM_GEOGRAPHIC.equals(o)) return this.readGeographicPoints(record, buffer); else if (AVKey.COORDINATE_SYSTEM_PROJECTED.equals(o)) return this.readProjectedPoints(record, buffer); else { // The Shapefile's coordinate system is unsupported. This should never happen because the coordinate system // is validated during initialization, but we check anyway. Let the caller catch and log the message. throw new WWRuntimeException(Logging.getMessage("generic.UnsupportedCoordinateSystem", o)); } } /** * Returns a {@link java.nio.DoubleBuffer} containing the (X,Y) tuples between the buffer's position and its limit. * The coordinates are assumed to be in an unspecified coordinate system and are not changed. * * @param record the record associated with the point coordinates, may be null. * @param buffer the buffer to read point coordinates from. * * @return a buffer containing the point coordinates. */ @SuppressWarnings({"UnusedDeclaration"}) protected DoubleBuffer readUnspecifiedPoints(ShapefileRecord record, ByteBuffer buffer) { // Create a view of the buffer as a doubles. return buffer.asDoubleBuffer(); } /** * Returns a {@link java.nio.DoubleBuffer} containing the geographic (longitude, latitude) tuples between the * buffer's position and its limit. This normalizes the geographic coordinates to the range +-90 latitude and +-180 * longitude if the record is non-null and {@link ShapefileRecord#isNormalizePoints()} returns true. * * @param record the record associated with the point coordinates, may be null. * @param buffer the buffer to read point coordinates from. * * @return a buffer containing the geographic point coordinates. */ protected DoubleBuffer readGeographicPoints(ShapefileRecord record, ByteBuffer buffer) { // Create a view of the buffer as a doubles. DoubleBuffer doubleBuffer = buffer.asDoubleBuffer(); // Normalize the buffer of geographic point coordinates if the record is flagged as needing normalization. if (record != null && record.isNormalizePoints()) { WWUtil.normalizeGeographicCoordinates(doubleBuffer); doubleBuffer.rewind(); } return doubleBuffer; } /** * Returns a {@link java.nio.DoubleBuffer} containing the projected (X,Y) tuples between the buffer's position and * its limit, converted to geographic coordinates (latitude,longitude). The returned coordinates are interpreted * according to the Shapefile's projection. This throws a {@link gov.nasa.worldwind.exception.WWRuntimeException} if * the projection is unsupported. * * @param record the record associated with the point coordinates, may be null. * @param buffer the buffer to read point coordinates from. * * @return a buffer containing geographic point coordinates converted form projected coordinates. * * @throws WWRuntimeException if the Shapefile's projection is unsupported. */ @SuppressWarnings({"UnusedDeclaration"}) protected DoubleBuffer readProjectedPoints(ShapefileRecord record, ByteBuffer buffer) { Object o = this.getValue(AVKey.PROJECTION_NAME); if (AVKey.PROJECTION_UTM.equals(o)) { // The Shapefile's coordinate system is UTM. Convert the UTM coordinates to geographic. The zone and hemisphere // parameters have already been validated in validateBounds. Integer zone = (Integer) this.getValue(AVKey.PROJECTION_ZONE); String hemisphere = (String) this.getValue(AVKey.PROJECTION_HEMISPHERE); // Create a view of the buffer as a doubles, and convert those coordinates from UTM to geographic. DoubleBuffer doubleBuffer = buffer.asDoubleBuffer(); WWUtil.convertUTMCoordinatesToGeographic(zone, hemisphere, doubleBuffer); doubleBuffer.rewind(); return doubleBuffer; } 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 WWRuntimeException(Logging.getMessage("generic.UnsupportedProjection", o)); } } //**************************************************************// //******************** 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. */ 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; } /** * Returns a bounding rectangle from the specified buffer. This reads four doubles and interprets them as a bounding * rectangle in the following order: (minX, minY, maxX, maxY). The returned rectangle's coordinates are interpreted * according to the Shapefile's coordinate system. This throws a {@link gov.nasa.worldwind.exception.WWRuntimeException} * if the coordinate system is unsupported. * * @param buffer the buffer to read from. * * @return a bounding rectangle with coordinates from the specified buffer. */ protected BoundingRectangle readBoundingRectangle(ByteBuffer buffer) { Object o = this.getValue(AVKey.COORDINATE_SYSTEM); if (!this.hasKey(AVKey.COORDINATE_SYSTEM)) return this.readUnspecifiedBoundingRectangle(buffer); else if (AVKey.COORDINATE_SYSTEM_GEOGRAPHIC.equals(o)) return this.readGeographicBoundingRectangle(buffer); else if (AVKey.COORDINATE_SYSTEM_PROJECTED.equals(o)) return this.readProjectedBoundingRectangle(buffer); else { // The Shapefile's coordinate system is unsupported. This should never happen because the coordinate system // is validated during initialization, but we check anyway. Let the caller catch and log the message. throw new WWRuntimeException(Logging.getMessage("generic.UnsupportedCoordinateSystem", o)); } } /** * Returns a bounding rectangle from the specified buffer. This reads four doubles and interprets them as a bounding * rectangle in the following order: (minX, minY, maxX, maxY). The coordinates are assumed to be in an unspecified * coordinate system and are not changed. * * @param buffer the buffer to read bounding rectangle coordinates from. * * @return a bounding rectangle with coordinates from the specified buffer. The rectangle's coordinates are ordered * as follows: (minY, maxY, minX, maxX). */ protected BoundingRectangle readUnspecifiedBoundingRectangle(ByteBuffer buffer) { // Read the bounding rectangle coordinates in the following order: minY, maxY, minX, maxX. BoundingRectangle rect = new BoundingRectangle(); rect.coords = this.readBoundingRectangleCoordinates(buffer); return rect; } /** * Returns a bounding rectangle from the specified buffer. This reads four doubles and interprets them as a * Geographic bounding rectangle in the following order: (minLat, maxLat, minLon, maxLon). If any of the coordinates * are out of the range -90/+90 latitude and -180/+180 longitude, this normalizes the coordinates and sets the * rectangle's {@link gov.nasa.worldwind.formats.shapefile.Shapefile.BoundingRectangle#isNormalized} property to * true. * * @param buffer the buffer to read bounding rectangle coordinates from. * * @return a bounding rectangle with coordinates from the specified buffer. The rectangle's coordinates are ordered * as follows: (minLat, maxLat, minLon, maxLon). */ protected BoundingRectangle readGeographicBoundingRectangle(ByteBuffer buffer) { // Read the bounding rectangle coordinates in the following order: minLat, maxLat, minLon, maxLon. BoundingRectangle rect = new BoundingRectangle(); rect.coords = this.readBoundingRectangleCoordinates(buffer); // 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 (rect.coords[0] < -90) { double normalizedLat = Angle.normalizedLatitude(Angle.fromDegrees(rect.coords[0])).degrees; rect.coords[0] = 90; rect.isNormalized = true; if (rect.coords[1] < normalizedLat) rect.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 (rect.coords[1] > 90) { double normalizedLat = Angle.normalizedLatitude(Angle.fromDegrees(rect.coords[1])).degrees; rect.coords[1] = 90; rect.isNormalized = true; if (rect.coords[0] > normalizedLat) rect.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 (rect.coords[2] < -180 || rect.coords[3] > 180) { rect.coords[2] = -180; rect.coords[3] = 180; rect.isNormalized = true; } return rect; } /** * Returns a bounding rectangle from the specified buffer. This reads four doubles and interprets them as a * projected bounding rectangle in the following order: (minX, maxX, minY, maxY). The projected rectangle is * converted to geographic coordinates before the rectangle is returned. The returned coordinates are interpreted * according to the Shapefile's projection. This throws a {@link gov.nasa.worldwind.exception.WWRuntimeException} if * the projection is unsupported. * * @param buffer the buffer to read bounding rectangle coordinates from. * * @return a bounding rectangle with coordinates from the specified buffer. The rectangle's coordinates are ordered * as follows: (minLat, maxLat, minLon, maxLon). * * @throws WWRuntimeException if the Shapefile's projection is unsupported. */ protected BoundingRectangle readProjectedBoundingRectangle(ByteBuffer buffer) { 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. double[] 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. Integer zone = (Integer) this.getValue(AVKey.PROJECTION_ZONE); String 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 WWRuntimeException(Logging.getMessage("generic.UnsupportedProjection", o)); } } /** * Reads a Shapefile bounding rectangle from the specified buffer. This reads four doubles and returns them as a * four-element array in the following order: (minY, maxY, minX, maxX). This ordering is consistent with the * ordering expected by {@link gov.nasa.worldwind.geom.Sector#fromDegrees(double[])}. * * @param buffer the buffer to read from. * * @return a four-element array ordered as follows: (minY, maxY, minX, maxX). */ protected double[] readBoundingRectangleCoordinates(ByteBuffer buffer) { // Read the bounding rectangle coordinates in the following order: minX, minY, maxX, maxY. double minx = buffer.getDouble(); double miny = buffer.getDouble(); double maxx = buffer.getDouble(); double maxy = buffer.getDouble(); // Return an array with bounding rectangle coordinates in the following order: minY, maxY, minX, maxX. return new double[] {miny, maxy, minx, maxx}; } //**************************************************************// //******************** Static Utilities **********************// //**************************************************************// /** * Indicates whether a specified shape type may contain optional measure values. * * @param shapeType the shape type to analyze. * * @return true if the shape type is one that may contain measure values. * * @throws IllegalArgumentException if shapeType is null. */ public static boolean isMeasureType(String shapeType) { if (shapeType == null) { String message = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return measureTypes.contains(shapeType); } /** * Indicates whether a specified shape type contains Z values. * * @param shapeType the shape type to analyze. * * @return true if the shape type is one that contains Z values. * * @throws IllegalArgumentException if shapeType is null. */ public static boolean isZType(String shapeType) { if (shapeType == null) { String message = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return zTypes.contains(shapeType); } /** * Indicates whether a specified shape type is {@link #SHAPE_NULL}. * * @param shapeType the shape type to analyze. * * @return true if the shape type is a null type. * * @throws IllegalArgumentException if shapeType is null. */ public static boolean isNullType(String shapeType) { if (shapeType == null) { String message = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return shapeType.equals(Shapefile.SHAPE_NULL); } /** * Indicates whether a specified shape type is either {@link #SHAPE_POINT}, {@link #SHAPE_POINT_M} or {@link * #SHAPE_POINT_Z}. * * @param shapeType the shape type to analyze. * * @return true if the shape type is a point type. * * @throws IllegalArgumentException if shapeType is null. */ public static boolean isPointType(String shapeType) { if (shapeType == null) { String message = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return shapeType.equals(Shapefile.SHAPE_POINT) || shapeType.equals(Shapefile.SHAPE_POINT_Z) || shapeType.equals(Shapefile.SHAPE_POINT_M); } /** * Indicates whether a specified shape type is either {@link #SHAPE_MULTI_POINT}, {@link #SHAPE_MULTI_POINT_M} or * {@link #SHAPE_MULTI_POINT_Z}. * * @param shapeType the shape type to analyze. * * @return true if the shape type is a mulit-point type. * * @throws IllegalArgumentException if shapeType is null. */ public static boolean isMultiPointType(String shapeType) { if (shapeType == null) { String message = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return shapeType.equals(Shapefile.SHAPE_MULTI_POINT) || shapeType.equals(Shapefile.SHAPE_MULTI_POINT_Z) || shapeType.equals(Shapefile.SHAPE_MULTI_POINT_M); } /** * Indicates whether a specified shape type is either {@link #SHAPE_POLYLINE}, {@link #SHAPE_POLYLINE_M} or {@link * #SHAPE_POLYLINE_Z}. * * @param shapeType the shape type to analyze. * * @return true if the shape type is a polyline type. * * @throws IllegalArgumentException if shapeType is null. */ public static boolean isPolylineType(String shapeType) { if (shapeType == null) { String message = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return shapeType.equals(Shapefile.SHAPE_POLYLINE) || shapeType.equals(Shapefile.SHAPE_POLYLINE_Z) || shapeType.equals(Shapefile.SHAPE_POLYLINE_M); } /** * Indicates whether a specified shape type is either {@link #SHAPE_POLYGON}, {@link #SHAPE_POLYGON_M} or {@link * #SHAPE_POLYGON_Z}. * * @param shapeType the shape type to analyze. * * @return true if the shape type is a polygon type. */ public static boolean isPolygonType(String shapeType) { if (shapeType == null) { String message = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return shapeType.equals(Shapefile.SHAPE_POLYGON) || shapeType.equals(Shapefile.SHAPE_POLYGON_Z) || shapeType.equals(Shapefile.SHAPE_POLYGON_M); } public String isExportFormatSupported(String mimeType) { if (KMLConstants.KML_MIME_TYPE.equalsIgnoreCase(mimeType)) return FORMAT_SUPPORTED; return Arrays.binarySearch(SHAPE_CONTENT_TYPES, mimeType) >= 0 ? FORMAT_SUPPORTED : FORMAT_NOT_SUPPORTED; } public void export(String mimeType, Object output) throws IOException, UnsupportedOperationException { if (mimeType == null) { String message = Logging.getMessage("nullValue.Format"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (output == null) { String message = Logging.getMessage("nullValue.OutputBufferIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } try { this.doExport(mimeType, output); } catch (XMLStreamException e) { Logging.logger().throwing(getClass().getName(), "export", e); throw new IOException(e); } } protected void doExport(String mimeType, Object output) throws IOException, XMLStreamException { XMLStreamWriter xmlWriter = null; XMLOutputFactory factory = XMLOutputFactory.newInstance(); boolean closeWriterWhenFinished = true; if (output instanceof XMLStreamWriter) { xmlWriter = (XMLStreamWriter) output; closeWriterWhenFinished = false; } else if (output instanceof Writer) { xmlWriter = factory.createXMLStreamWriter((Writer) output); } else if (output instanceof OutputStream) { xmlWriter = factory.createXMLStreamWriter((OutputStream) output); } if (xmlWriter == null) { String message = Logging.getMessage("Export.UnsupportedOutputObject"); Logging.logger().warning(message); throw new IllegalArgumentException(message); } if (KMLConstants.KML_MIME_TYPE.equals(mimeType)) exportAsKML(xmlWriter); else exportAsXML(xmlWriter); xmlWriter.flush(); if (closeWriterWhenFinished) xmlWriter.close(); } protected void exportAsXML(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException { xmlWriter.writeStartElement("Shapefile"); xmlWriter.writeCharacters("\n"); while (this.hasNext()) { try { ShapefileRecord nr = this.nextRecord(); if (nr == null) continue; nr.exportAsXML(xmlWriter); xmlWriter.writeCharacters("\n"); } catch (Exception e) { String message = Logging.getMessage("Export.Exception.ShapefileRecord"); Logging.logger().log(Level.WARNING, message, e); continue; // keep processing the records } } xmlWriter.writeEndElement(); // Shapefile } protected void exportAsKML(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException { while (this.hasNext()) { try { ShapefileRecord nr = this.nextRecord(); if (nr == null) continue; nr.exportAsKML(xmlWriter); } catch (Exception e) { String message = Logging.getMessage("Export.Exception.ShapefileRecord"); Logging.logger().log(Level.WARNING, message, e); continue; // keep processing the records } } } public void printInfo(boolean printCoordinates) { while (this.hasNext()) { this.nextRecord().printInfo(printCoordinates); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy