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

org.tinfour.gis.utils.VertexReaderShapefile Maven / Gradle / Ivy

/* --------------------------------------------------------------------
 * Copyright 2015 Gary W. Lucas.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ---------------------------------------------------------------------
 */

 /*
 * -----------------------------------------------------------------------
 *
 * Revision History:
 * Date Name Description
 * ------  --------- -------------------------------------------------
 * 12/2018 G. Lucas  Created
 *
 * Notes:
 *  * Notes:
 *   Future Work: This module has some logic for handling the
 *   the Well-Known Format used by .prj files. It should be replaced with
 *   a centralized treatment.  See also the ShapefileReader for this discussion.
 *
 * -----------------------------------------------------------------------
 */
package org.tinfour.gis.utils;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.tinfour.common.IMonitorWithCancellation;
import org.tinfour.common.Vertex;
import org.tinfour.gis.shapefile.DbfField;
import org.tinfour.gis.shapefile.DbfFileReader;
import org.tinfour.gis.shapefile.ShapefileReader;
import org.tinfour.gis.shapefile.ShapefileRecord;
import org.tinfour.utils.LinearUnits;
import org.tinfour.utils.loaders.CoordinatePair;
import org.tinfour.utils.loaders.ICoordinateTransform;
import org.tinfour.utils.loaders.IVertexReader;
import org.tinfour.utils.loaders.SimpleGeographicTransform;

/**
 * A utility for loading vertices from a file for testing
 */
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public class VertexReaderShapefile implements IVertexReader, Closeable {

  final File file;
  final String rootPath;
  ShapefileReader reader;

  double xMin, xMax, yMin, yMax, zMin, zMax;

  double timeForLoad;

  boolean geographicCoordinates;
  LinearUnits linearUnits = LinearUnits.UNKNOWN;
  ICoordinateTransform coordinateTransform;
  IVerticalCoordinateTransform verticalCoordinateTransform;
  String dbfFieldForZ;

  /**
   * Construct an instance for the specified Shapefile
   *
   * @param file a Shapefile reference
   * @throws java.io.IOException in the event of an unrecoverable I/O condition
   *
   */
  public VertexReaderShapefile(File file) throws IOException {
    // TO DO: The linear units should be in the PRJ file
    this.file = file;

    String testExt = null;
    String path = file.getPath();
    int lastPeriod = path.lastIndexOf('.');
    if (lastPeriod > 0) {
      testExt = path.substring(lastPeriod + 1, path.length());
    }
    if (!"shp".equalsIgnoreCase(testExt)) {
      throw new IllegalArgumentException("File must be of type .shp");
    }
    rootPath = path.substring(0, lastPeriod);
    reader = openFile(file);
  }

  /**
   * Sets the name of the field in the DBF file to use as a source for Z
   * coordinates for data.
   *
   * @param dbfFieldForZ a valid string, or a null to use the Z coordinates from
   * the Shapefile geometry file (the 0shp file).
   */
  public void setDbfFieldForZ(String dbfFieldForZ) {
    if (dbfFieldForZ != null) {
      this.dbfFieldForZ = dbfFieldForZ.trim();
      if (this.dbfFieldForZ.isEmpty()) {
        this.dbfFieldForZ = null;
      }
    } else {
      this.dbfFieldForZ = null;
    }
  }

  
  /**
   * Sets the vertical coordinate transform to be used when reading the
   * file (if any).
   * @param verticalTransform a valid instance if a transform is to be
   * applies; a null reference if no transform is required.
   */
  public void setVerticalCoordinateTransform(IVerticalCoordinateTransform verticalTransform){
    this.verticalCoordinateTransform = verticalTransform;
  }
  
  
  /**
   * Gets the minimum x coordinate in the sample
   *
   * @return a valid floating point value
   */
  @Override
  public double getXMin() {
    return xMin;
  }

  /**
   * Gets the maximum x coordinate in the sample
   *
   * @return a valid floating point value
   */
  @Override
  public double getXMax() {
    return xMax;
  }

  /**
   * Gets the minimum y coordinate in the sample
   *
   * @return a valid floating point value
   */
  @Override
  public double getYMin() {
    return yMin;
  }

  /**
   * Gets the maximum y coordinate in the sample
   *
   * @return a valid floating point value
   */
  @Override
  public double getYMax() {
    return yMax;
  }

  /**
   * Gets the minimum z coordinate in the sample
   *
   * @return a valid floating point value
   */
  @Override
  public double getZMin() {
    return zMin;
  }

  /**
   * Gets the maximum z coordinate in the sample
   *
   * @return a valid floating point value
   */
  @Override
  public double getZMax() {
    return zMax;
  }

  /**
   * Gets the total time to load a file (including the time required for
   * pre-sort if enabled).
   *
   * @return a time in milliseconds
   */
  public double getTimeForLoad() {
    return timeForLoad;
  }

  /**
   * Indicates whether the source data was in geographic coordinates
   *
   * @return true if the source data used geographic coordinates; otherwise,
   * false.
   */
  @Override
  public boolean isSourceInGeographicCoordinates() {
    return geographicCoordinates;
  }

  /**
   * Read the records from the Shapefile and use them to populate vertices. The
   * loader has the option of loading the z coordinate from either the main
   * Shapefile itself (the SHP file) or from the associated DBF file. If you
   * wish to use a field in the DBF file as a z coordinate, specify the name of
   * the field as an argument. If you wish to use the z coordinate from the
   * Shapefile, specify a null or empty string. If a null or empty string is
   * specified, and the Shapefile does not contain a feature type that provides
   * z coordinates, the z coordinates will be uniformly populated with zeroes.
   * 

* The index of the vertex is set to be the Shapefile record number. Thus many * vertices may be assigned with the same record number, particularly if the * input is a polygon or line feature. * @param monitor an optional progress monitor, or null if not required. * @return a valid, potentially empty list of vertices * @throws IOException in the event of an unrecoverable I/O condition */ @SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts") @Override public List read(IMonitorWithCancellation monitor) throws IOException { double mainFileSize = 0; if(monitor!=null ){ mainFileSize = reader.getFileSize(); } DbfFileReader dbfReader = null; DbfField zField = null; boolean useShapefileZ = true; CoordinatePair scratch = new CoordinatePair(); if (dbfFieldForZ != null) { File dbfFile = reader.getCoFile("DBF"); dbfReader = reader.getDbfFileReader(); zField = dbfReader.getFieldByName(dbfFieldForZ); if (zField == null) { try { dbfReader.close(); } catch (IOException dontCare) { // NOPMD no action required. } throw new IllegalArgumentException("The specified field " + dbfFieldForZ + " was not found in " + dbfFile.getName()); } if (!zField.isNumeric()) { try { dbfReader.close(); } catch (IOException dontCare) { // NOPMD no action required. } throw new IllegalArgumentException( "The specified field " + dbfFieldForZ + " is not numeric in" + dbfFile.getName()); } useShapefileZ = false; } List vList = new ArrayList<>(); ShapefileRecord record = null; xMin = Double.POSITIVE_INFINITY; yMin = Double.POSITIVE_INFINITY; zMin = Double.POSITIVE_INFINITY; xMax = Double.NEGATIVE_INFINITY; yMax = Double.NEGATIVE_INFINITY; zMax = Double.NEGATIVE_INFINITY; int nRecordsRead = 0; while (reader.hasNext()) { if( monitor!=null && mainFileSize>0 && (nRecordsRead%1000)==0 ){ double filePos = reader.getFilePosition(); monitor.reportProgress((int) (100.0 * filePos/mainFileSize)); } record = reader.readNextRecord(record); int recNo = record.recordNumber; double[] xyz = record.xyz; double z = 0; if (dbfReader != null && zField != null) { dbfReader.readField(recNo, zField); z = zField.getDouble(); } for (int i = 0; i < record.nPoints; i++) { int index =i*3; double x = xyz[index]; double y = xyz[index+1]; if (useShapefileZ) { z = xyz[index+2]; } if (coordinateTransform != null) { boolean status = coordinateTransform.forward(x, y, scratch); if (!status) { throw new IOException( "Invalid transformation for coordinates in record " + recNo + ": " + x + "," + y); } x = scratch.x; y = scratch.y; } if(verticalCoordinateTransform!=null){ z = verticalCoordinateTransform.transform(recNo, z); } vList.add(new Vertex(x, y, z, recNo)); if (x < xMin) { xMin = x; } if (y < yMin) { yMin = y; } if (z < zMin) { zMin = z; } if (x > xMax) { xMax = x; } if (y > yMax) { yMax = y; } if (z > zMax) { zMax = z; } } } if (dbfReader != null) { dbfReader.close(); } return vList; } private ShapefileReader openFile(File file) throws IOException, IllegalArgumentException { File target = file; ShapefileReader reader = new ShapefileReader(target); try { checkForGeographicCoordinates(reader); } catch (IOException ioex) { try { if (reader != null) { reader.close(); reader = null; } throw ioex; } catch (IOException dontCare) { // NOPMD no action required } } return reader; } private void checkForGeographicCoordinates(ShapefileReader reader) throws IOException { File target = reader.getCoFile("prj"); if (target != null) { try (FileInputStream fins = new FileInputStream(target)) { StringBuilder sb = new StringBuilder(); byte[] buffer = new byte[8192]; int n; while ((n = fins.read(buffer)) > 0) { for (int i = 0; i < n; i++) { sb.append((char) (buffer[i])); } } String content = sb.toString().toUpperCase(); int indexPROJ = content.indexOf("PROJ"); if (indexPROJ >= 0) { return; // not geographic } int indexGEOCS = content.indexOf("GEOCS"); if (indexGEOCS > 0) { // definitely geographic geographicCoordinates = true; } int indexUnit=content.indexOf("UNIT"); if(indexUnit>0){ if(content.indexOf("FOOT")>indexUnit || content.indexOf("FEET")>indexUnit){ linearUnits = LinearUnits.FEET; } } } catch (IOException ioex) { // NOPMD no action required } } double x0 = reader.getMinX(); double y0 = reader.getMinY(); double x1 = reader.getMaxX(); double y1 = reader.getMaxY(); double dx = x1 - x0; double dy = y1 - y0; geographicCoordinates = dx <= 360 && dy < 90 && -180 <= x0 && x1 < 180 && -90 <= y0 && y1 <= 90; if (geographicCoordinates) { double xCenter = (x0 + x1) / 2.0; double yCenter = (y0 + y1) / 2.0; coordinateTransform = new SimpleGeographicTransform( yCenter, xCenter, linearUnits); } } @Override public void close() throws IOException { reader.close(); } @Override public ICoordinateTransform getCoordinateTransform() { return coordinateTransform; } @Override public void setCoordinateTransform(ICoordinateTransform transform) { coordinateTransform = transform; geographicCoordinates = transform instanceof SimpleGeographicTransform; } /** * Gets the linear units for the horizontal coordinate system from * the Shapefile. This value is typically obtained from the * PRJ file associated with the Shapefile. * @return a valid enumeration instance */ public LinearUnits getLinearUnits(){ return linearUnits; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy