457 lines
13 KiB
PHP
457 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* This class is under GPL Licencense Agreement
|
|
* @author Juan Carlos Gonzalez Ulloa <jgonzalez@innox.com.mx>
|
|
* Innovacion Inteligente S de RL de CV (Innox)
|
|
* Lopez Mateos Sur 2077 - Z16
|
|
* Col. Jardines de Plaza del Sol
|
|
* Guadalajara, Jalisco
|
|
* CP 44510
|
|
* Mexico
|
|
*
|
|
* Class to read SHP files and modify the DBF related information
|
|
* Just create the object and all the records will be saved in $shp->records
|
|
* Each record has the "shp_data" and "dbf_data" arrays with the record information
|
|
* You can modify the DBF information using the $shp_record->setDBFInformation($data)
|
|
* The $data must be an array with the DBF's row data.
|
|
*
|
|
* Performance issues:
|
|
* Because the class opens and fetches all the information (records/dbf info)
|
|
* from the file, the loading time and memory amount neede may be way to much.
|
|
* Example:
|
|
* 15 seconds loading a 210907 points shape file
|
|
* 60Mb memory limit needed
|
|
* Athlon XP 2200+
|
|
* Mandrake 10 OS
|
|
*
|
|
*
|
|
*
|
|
* Edited by David Granqvist March 2008 for better performance on large files
|
|
* This version only get the information it really needs
|
|
* Get one record at a time to save memory, means that you can work with very large files.
|
|
* Does not load the information until you tell it too (saves time)
|
|
* Added an option to not read the polygon points can be handy sometimes, and saves time :-)
|
|
*
|
|
* Example:
|
|
|
|
//sets the options to show the polygon points, 'noparts' => true would skip that and save time
|
|
$options = array('noparts' => false);
|
|
$shp = new ShapeFile("../../php/shapefile/file.shp",$options);
|
|
|
|
//Dump the ten first records
|
|
$i = 0;
|
|
while ($record = $shp->getNext() and $i<10) {
|
|
$dbf_data = $record->getDbfData();
|
|
$shp_data = $record->getShpData();
|
|
//Dump the information
|
|
var_dump($dbf_data);
|
|
var_dump($shp_data);
|
|
$i++;
|
|
}
|
|
*
|
|
*/
|
|
|
|
// Configuration
|
|
define("SHOW_ERRORS", true);
|
|
define("DEBUG", false);
|
|
|
|
|
|
// Constants
|
|
define("XY_POINT_RECORD_LENGTH", 16);
|
|
|
|
|
|
// Strings
|
|
define("ERROR_FILE_NOT_FOUND", "SHP File not found [%s]");
|
|
define("INEXISTENT_RECORD_CLASS", "Unable to determine shape record type [%i]");
|
|
define("INEXISTENT_FUNCTION", "Unable to find reading function [%s]");
|
|
define("INEXISTENT_DBF_FILE", "Unable to open (read/write) SHP's DBF file [%s]");
|
|
define("INCORRECT_DBF_FILE", "Unable to read SHP's DBF file [%s]");
|
|
define("UNABLE_TO_WRITE_DBF_FILE", "Unable to write DBF file [%s]");
|
|
|
|
|
|
|
|
class ShapeFile{
|
|
private $file_name;
|
|
private $fp;
|
|
//Used to fasten up the search between records;
|
|
private $dbf_filename = null;
|
|
//Starting position is 100 for the records
|
|
private $fpos = 100;
|
|
|
|
private $error_message = "";
|
|
private $show_errors = SHOW_ERRORS;
|
|
|
|
private $shp_bounding_box = array();
|
|
private $shp_type = 0;
|
|
|
|
private $records;
|
|
|
|
function ShapeFile($file_name,$options){
|
|
$this->options = $options;
|
|
|
|
$this->file_name = $file_name;
|
|
//_d("Opening [$file_name]");
|
|
if(!is_readable($file_name)){
|
|
return $this->setError( sprintf(ERROR_FILE_NOT_FOUND, $file_name) );
|
|
}
|
|
|
|
$this->fp = fopen($this->file_name, "rb");
|
|
|
|
$this->_fetchShpBasicConfiguration();
|
|
|
|
//Set the dbf filename
|
|
$this->dbf_filename = processDBFFileName($this->file_name);
|
|
|
|
}
|
|
|
|
|
|
public function getError(){
|
|
return $this->error_message;
|
|
}
|
|
|
|
|
|
function __destruct()
|
|
{
|
|
$this->closeFile();
|
|
}
|
|
|
|
// Data fetchers
|
|
private function _fetchShpBasicConfiguration(){
|
|
//_d("Reading basic information");
|
|
fseek($this->fp, 32, SEEK_SET);
|
|
$this->shp_type = readAndUnpack("i", fread($this->fp, 4));
|
|
//_d("SHP type detected: ".$this->shp_type);
|
|
|
|
$this->shp_bounding_box = readBoundingBox($this->fp);
|
|
////_d("SHP bounding box detected: miX=".$this->shp_bounding_box["xmin"]." miY=".$this->shp_bounding_box["ymin"]." maX=".$this->shp_bounding_box["xmax"]." maY=".$this->shp_bounding_box["ymax"]);
|
|
}
|
|
|
|
|
|
|
|
public function getNext(){
|
|
if (!feof($this->fp)) {
|
|
fseek($this->fp, $this->fpos);
|
|
$shp_record = new ShapeRecord($this->fp, $this->dbf_filename,$this->options);
|
|
if($shp_record->getError() != ""){
|
|
return false;
|
|
}
|
|
$this->fpos = $shp_record->getNextRecordPosition();
|
|
return $shp_record;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Takes too much memory
|
|
function _fetchRecords(){
|
|
fseek($this->fp, 100);
|
|
while(!feof($this->fp)){
|
|
$shp_record = new ShapeRecord($this->fp, $this->file_name);
|
|
if($shp_record->error_message != ""){
|
|
return false;
|
|
}
|
|
$this->records[] = $shp_record;
|
|
}
|
|
}
|
|
*/
|
|
|
|
//Not Used
|
|
/* private function getDBFHeader(){
|
|
$dbf_data = array();
|
|
if(is_readable($dbf_data)){
|
|
$dbf = dbase_open($this->dbf_filename , 1);
|
|
// solo en PHP5 $dbf_data = dbase_get_header_info($dbf);
|
|
echo dbase_get_header_info($dbf);
|
|
}
|
|
}
|
|
*/
|
|
|
|
// General functions
|
|
private function setError($error){
|
|
$this->error_message = $error;
|
|
if($this->show_errors){
|
|
echo $error."\n";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function closeFile(){
|
|
if($this->fp){
|
|
fclose($this->fp);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* ShapeRecord
|
|
*
|
|
*/
|
|
class ShapeRecord{
|
|
private $fp;
|
|
private $fpos = null;
|
|
|
|
private $dbf = null;
|
|
|
|
private $record_number = null;
|
|
private $content_length = null;
|
|
private $record_shape_type = null;
|
|
|
|
private $error_message = "";
|
|
|
|
private $shp_data = array();
|
|
private $dbf_data = array();
|
|
|
|
private $file_name = "";
|
|
|
|
private $record_class = array( 0 => "RecordNull",
|
|
1 => "RecordPoint",
|
|
8 => "RecordMultiPoint",
|
|
3 => "RecordPolyLine",
|
|
5 => "RecordPolygon");
|
|
|
|
function ShapeRecord(&$fp, $file_name,$options){
|
|
$this->fp = $fp;
|
|
$this->fpos = ftell($fp);
|
|
$this->options = $options;
|
|
|
|
//_d("Shape record created at byte ".ftell($fp));
|
|
|
|
if (feof($fp)) {
|
|
echo "end ";
|
|
exit;
|
|
}
|
|
$this->_readHeader();
|
|
|
|
$this->file_name = $file_name;
|
|
|
|
}
|
|
|
|
public function getNextRecordPosition(){
|
|
$nextRecordPosition = $this->fpos + ((4 + $this->content_length )* 2);
|
|
return $nextRecordPosition;
|
|
}
|
|
|
|
private function _readHeader(){
|
|
$this->record_number = readAndUnpack("N", fread($this->fp, 4));
|
|
$this->content_length = readAndUnpack("N", fread($this->fp, 4));
|
|
$this->record_shape_type = readAndUnpack("i", fread($this->fp, 4));
|
|
|
|
//_d("Shape Record ID=".$this->record_number." ContentLength=".$this->content_length." RecordShapeType=".$this->record_shape_type."\nEnding byte ".ftell($this->fp)."\n");
|
|
}
|
|
|
|
private function getRecordClass(){
|
|
if(!isset($this->record_class[$this->record_shape_type])){
|
|
//_d("Unable to find record class ($this->record_shape_type) [".getArray($this->record_class)."]");
|
|
return $this->setError( sprintf(INEXISTENT_RECORD_CLASS, $this->record_shape_type) );
|
|
}
|
|
//_d("Returning record class ($this->record_shape_type) ".$this->record_class[$this->record_shape_type]);
|
|
return $this->record_class[$this->record_shape_type];
|
|
}
|
|
|
|
private function setError($error){
|
|
$this->error_message = $error;
|
|
return false;
|
|
}
|
|
|
|
public function getError(){
|
|
return $this->error_message;
|
|
}
|
|
|
|
public function getShpData(){
|
|
$function_name = "read".$this->getRecordClass();
|
|
|
|
//_d("Calling reading function [$function_name] starting at byte ".ftell($fp));
|
|
|
|
if(function_exists($function_name)){
|
|
$this->shp_data = $function_name($this->fp,$this->options);
|
|
} else {
|
|
$this->setError( sprintf(INEXISTENT_FUNCTION, $function_name) );
|
|
}
|
|
|
|
return $this->shp_data;
|
|
}
|
|
|
|
public function getDbfData(){
|
|
|
|
$this->_fetchDBFInformation();
|
|
|
|
return $this->dbf_data;
|
|
}
|
|
|
|
private function _openDBFFile($check_writeable = false){
|
|
$check_function = $check_writeable ? "is_writable" : "is_readable";
|
|
if($check_function($this->file_name)){
|
|
$this->dbf = dbase_open($this->file_name, ($check_writeable ? 2 : 0));
|
|
if(!$this->dbf){
|
|
$this->setError( sprintf(INCORRECT_DBF_FILE, $this->file_name) );
|
|
}
|
|
} else {
|
|
$this->setError( sprintf(INEXISTENT_DBF_FILE, $this->file_name) );
|
|
}
|
|
}
|
|
|
|
private function _closeDBFFile(){
|
|
if($this->dbf){
|
|
dbase_close($this->dbf);
|
|
$this->dbf = null;
|
|
}
|
|
}
|
|
|
|
private function _fetchDBFInformation(){
|
|
$this->_openDBFFile();
|
|
if($this->dbf) {
|
|
$this->dbf_data = dbase_get_record_with_names($this->dbf, $this->record_number);
|
|
} else {
|
|
$this->setError( sprintf(INCORRECT_DBF_FILE, $this->file_name) );
|
|
}
|
|
$this->_closeDBFFile();
|
|
}
|
|
|
|
public function setDBFInformation($row_array){
|
|
$this->_openDBFFile(true);
|
|
if($this->dbf) {
|
|
unset($row_array["deleted"]);
|
|
|
|
if(!dbase_replace_record($this->dbf, array_values($row_array), $this->record_number)){
|
|
$this->setError( sprintf(UNABLE_TO_WRITE_DBF_FILE, $this->file_name) );
|
|
} else {
|
|
$this->dbf_data = $row_array;
|
|
}
|
|
} else {
|
|
$this->setError( sprintf(INCORRECT_DBF_FILE, $this->file_name) );
|
|
}
|
|
$this->_closeDBFFile();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Reading functions
|
|
*/
|
|
|
|
function readRecordNull(&$fp, $read_shape_type = false,$options = null){
|
|
$data = array();
|
|
if($read_shape_type) $data += readShapeType($fp);
|
|
//_d("Returning Null shp_data array = ".getArray($data));
|
|
return $data;
|
|
}
|
|
$point_count = 0;
|
|
function readRecordPoint(&$fp, $create_object = false,$options = null){
|
|
global $point_count;
|
|
$data = array();
|
|
|
|
$data["x"] = readAndUnpack("d", fread($fp, 8));
|
|
$data["y"] = readAndUnpack("d", fread($fp, 8));
|
|
|
|
////_d("Returning Point shp_data array = ".getArray($data));
|
|
$point_count++;
|
|
return $data;
|
|
}
|
|
|
|
function readRecordMultiPoint(&$fp,$options = null){
|
|
$data = readBoundingBox($fp);
|
|
$data["numpoints"] = readAndUnpack("i", fread($fp, 4));
|
|
//_d("MultiPoint numpoints = ".$data["numpoints"]);
|
|
|
|
for($i = 0; $i <= $data["numpoints"]; $i++){
|
|
$data["points"][] = readRecordPoint($fp);
|
|
}
|
|
|
|
//_d("Returning MultiPoint shp_data array = ".getArray($data));
|
|
return $data;
|
|
}
|
|
|
|
function readRecordPolyLine(&$fp,$options = null){
|
|
$data = readBoundingBox($fp);
|
|
$data["numparts"] = readAndUnpack("i", fread($fp, 4));
|
|
$data["numpoints"] = readAndUnpack("i", fread($fp, 4));
|
|
|
|
//_d("PolyLine numparts = ".$data["numparts"]." numpoints = ".$data["numpoints"]);
|
|
if (isset($options['noparts']) && $options['noparts']==true) {
|
|
//Skip the parts
|
|
$points_initial_index = ftell($fp)+4*$data["numparts"];
|
|
$points_read = $data["numpoints"];
|
|
}
|
|
else{
|
|
for($i=0; $i<$data["numparts"]; $i++){
|
|
$data["parts"][$i] = readAndUnpack("i", fread($fp, 4));
|
|
//_d("PolyLine adding point index= ".$data["parts"][$i]);
|
|
}
|
|
|
|
$points_initial_index = ftell($fp);
|
|
|
|
//_d("Reading points; initial index = $points_initial_index");
|
|
$points_read = 0;
|
|
foreach($data["parts"] as $part_index => $point_index){
|
|
//fseek($fp, $points_initial_index + $point_index);
|
|
//_d("Seeking initial index point [".($points_initial_index + $point_index)."]");
|
|
if(!isset($data["parts"][$part_index]["points"]) || !is_array($data["parts"][$part_index]["points"])){
|
|
$data["parts"][$part_index] = array();
|
|
$data["parts"][$part_index]["points"] = array();
|
|
}
|
|
while( ! in_array( $points_read, $data["parts"]) && $points_read < $data["numpoints"] && !feof($fp)){
|
|
$data["parts"][$part_index]["points"][] = readRecordPoint($fp, true);
|
|
$points_read++;
|
|
}
|
|
}
|
|
}
|
|
|
|
fseek($fp, $points_initial_index + ($points_read * XY_POINT_RECORD_LENGTH));
|
|
|
|
//_d("Seeking end of points section [".($points_initial_index + ($points_read * XY_POINT_RECORD_LENGTH))."]");
|
|
return $data;
|
|
}
|
|
|
|
function readRecordPolygon(&$fp,$options = null){
|
|
//_d("Polygon reading; applying readRecordPolyLine function");
|
|
return readRecordPolyLine($fp,$options);
|
|
}
|
|
|
|
/**
|
|
* General functions
|
|
*/
|
|
function processDBFFileName($dbf_filename){
|
|
//_d("Received filename [$dbf_filename]");
|
|
if(!strstr($dbf_filename, ".")){
|
|
$dbf_filename .= ".dbf";
|
|
}
|
|
|
|
if(substr($dbf_filename, strlen($dbf_filename)-3, 3) != "dbf"){
|
|
$dbf_filename = substr($dbf_filename, 0, strlen($dbf_filename)-3)."dbf";
|
|
}
|
|
//_d("Ended up like [$dbf_filename]");
|
|
return $dbf_filename;
|
|
}
|
|
|
|
function readBoundingBox(&$fp){
|
|
$data = array();
|
|
$data["xmin"] = readAndUnpack("d",fread($fp, 8));
|
|
$data["ymin"] = readAndUnpack("d",fread($fp, 8));
|
|
$data["xmax"] = readAndUnpack("d",fread($fp, 8));
|
|
$data["ymax"] = readAndUnpack("d",fread($fp, 8));
|
|
|
|
//_d("Bounding box read: miX=".$data["xmin"]." miY=".$data["ymin"]." maX=".$data["xmax"]." maY=".$data["ymax"]);
|
|
return $data;
|
|
}
|
|
|
|
function readAndUnpack($type, $data){
|
|
if(!$data) return $data;
|
|
return current(unpack($type, $data));
|
|
}
|
|
|
|
function _d($debug_text){
|
|
if(DEBUG){
|
|
echo $debug_text."\n";
|
|
}
|
|
}
|
|
|
|
function getArray($array){
|
|
ob_start();
|
|
print_r($array);
|
|
$contents = ob_get_contents();
|
|
ob_get_clean();
|
|
return $contents;
|
|
}
|
|
?>
|