-
-
Notifications
You must be signed in to change notification settings - Fork 367
PerlMapScriptExamples35ex8
Thomas Bonfort edited this page Apr 6, 2012
·
2 revisions
#!perl
#!/usr/bin/perl
#
# Copyright (C) 2002, Lowell Filak.
# You may distribute this file under the terms of the Artistic
# License.
#
# Given the name of an existing palm database and the name
# of an existing point shapefile this routine will append the points
# in the pdb to the shapefile.
# Given just the name of an existing shapefile the routine will attempt
# to use pilot-xfer to download the pdb file and append the points to the
# shapefile.
# Given just the name of an existing pdb file this routine will create a
# new shapefile of the points in the pdb file.
# Given neither name this routine will attempt to use pilot-xfer to download
# the pdb file and create a new point shapefile from it.
# Notes: The fields in the pdb file should match the fields in the
# existing shapefile (dbf) or the assignments will either be
# wrong or will cause the routine to bomb.
# The pdb file for download is assumed to be StickeDB.pdb and the routine
# is written to read sticke style pdb files only.
# The pdb reading section of this routine is not complete but is setup
# somewhat generic and should be extendable to any sticke database
# schema.
# The pilot-xfer download line assumes the default pilot device
# (/dev/pilot) exists.
# The routine also assumes a *n*x system, please change command
# lines accordingly.
# If nad support is needed in proj.4 please verify that the ntv1_can.dat
# file is included before compiling. If not, grab a newer release.
#
# Required modules are mapscript (installed as part of make install
# http://mapserver.gis.umn.edu),
# Getopt (normally included with Perl),
# Palm (p5-Palm-1.1.5 http://theoryx5.uwinnipeg.ca/CPAN/data/p5-Palm/Palm/Raw.html),
# & XBase (cpan).
# Please download StickeDB.pdb also.
#
# Additional requirements are: a working pilot-xfer (pilot-link http://www.pilot-link.org/)
# installation,
# a working StickePad.prc and StickePlates.prc (StickeV2Programs
# http://www.cs.ukc.ac.uk/projects/mobicomp/Fieldwork/) on a
# PalmOS handheld device, & a working proj.4 install compiled with
# the optional nad files in place and with the cs2cs
# command working (www.remotesensing.org/proj/).
#
# Current GPS information: Palm IIIX-PDA, Garmin Etrex Summit-GPS, Blue
# Hills Innovations-Garmin2Palm cable (http://www.blue-hills-innovations.com).
#
# Suggested run line = ./tcounts.pl -pdbfile=StickeDB -sfile=traffic
#
# Syntax: tcounts.pl -pdbfile=[in_pdb_filename] -sfile=[out_shapefile_name]
#
# Include the pdb and pdb-raw modules.
use Palm::PDB;
use Palm::Raw;
#
# Include the mapscript module.
use mapscript;
#
# Include the xbase module for creating the dbf records.
use XBase;
#
# Include the getopt module to read input.
use Getopt::Long;
#
# Helpful definitions for StickeDB.pdb:
# I view the structure as very similar to an rdbms.
# Database - refers to the pdb file itself.
# Table - refers to the rdbms-like table name included on every record.
# Note: Each record can belong to a different table or even a different
# table deffinition under the same table name!
# Note: Tables are refered to as 'templates' inside sticke.
# Record - refers to the entire 'data' portion returned by the pdb->data obj.
# Note: Records are refered to as 'notes' inside of sticke.
# Field - refers to the section of the 'data' portion of the record which
# spans from the beginning of one field name to the beginning of the
# next.
# Note: Fields are refered to as both 'fields' and 'items' inside of sticke.
# Part - refers to the sections of a field that define the schema of the
# field (schema - data type, constraints, etc).
#
# Grab the file names from the input.
&GetOptions('pdbfile=s' => \$pdbfile, 'sfile=s' => \$sfile);
if ( !$sfile ) {
#
# Create a unique name for the shapefile based on date and process number.
#
# Grab the time.
($sec,$min,$hr,$mdy,$mnth,$yr,$wdy,$ydy,$isdst) = localtime;
#
# Grab the process id.
$spid = $$;
#
# Create the name & make sure it is no longer than 8 characters.
$sfile = substr("$hr$min$sec$spid", -8);
}
if( !$pdbfile ) {
#
# Download the pdb file.
system("pilot-xfer -f StickeDB");
#
# Set the pdb file name.
$pdbfile = 'StickeDB';
}
#
# Create the pdb object on the pdb file.
my $pdb = new Palm::PDB;
$pdb->Load("$pdbfile.pdb");
#
# How many pdb records are there.
my @records = @{$pdb->{records}};
my $numrecs = scalar(@records);
#
# Create the information array for each field/data type.
# Note: The routine is incomplete at this time because we currently only use
# 7-number & 4-location but all types are here for documentation.
my @types = ('bearing', 'boolean', 'date', 'textline', 'location', 'unused', 'notepad', 'number', 'picklist', 'subnote', 'time');
#
# Create the field type number-of-parts array.
my @parts = (0,0,0,0,23,0,0,11,0,0,0);
#
# Create the field types unpack string array.
# For location [offset+10]=dontno5, [offset+11]=latdegrees,
# [offset+12]=dontno6, [offset+13]=latminuteswhole,
# [offset+14]=dontno7, [offset+15]=latminutesdecimal,
# [offset+16]=elevation, [offset+17]=dontno8, [offset+18]=longdegrees,
# [offset+19]=dontno9, [offset+20]=longminuteswhole,
# [offset+21]=dontno10, [offset+22]=longminutesdecimal
# Note: At this point I can't find the NSEW/+- indication for lat/lon!
# For number [offset+10]=number
my @ustring = (' a*', ' a*', ' a*', ' a*', ' l n a2 n a2 n s a2 n a2 n a2 n', ' a*', ' a*', ' N', ' a*', ' a*', ' a*');
#
# Create the array for the pdb-field-type to dbf-field-type conversion.
my @dbfftype = ('C', 'L', 'D', 'C', 'C', 'C', 'C', 'N', 'C', 'C', 'N');
#
# Create the array for the pdb-field-size to dbf-field-size conversion.
my @dbffsize = ('255', '1', '8', '255', '31', '0', '255', '11', '255', '255', '10');
#
# Create the array for the pdb-field-decimal to dbf-field-decimal conversion.
# No need for this now. All decimals should be null going into the dbf file.
#
# Create the array for tracking field offsets.
# To be used later while creating dbf records for each gps point.
my @offsets = ();
#
# Create the initial unpack string.
my $unpackstr = "";
#
# Create the initial data array.
my @recordinfo = ();
#
# Initialize the dbf record count to 0.
my $dbfreccnt = 0;
#
# Does the dbf file already exist or does it yet to exist.
if ( -e "$sfile.dbf" ) {
#
# Open the existing dbf file for appending to.
$dbh = new XBase "$sfile.dbf" or die XBase->errstr;
#
# To be able to increment the record # starting at the last existing
# record how many records are there.
$dbfreccnt = $dbh->last_record + 1;
}
else {
$dbfreccnt = -1;
}
#
# Does the shapefile already exist or is it yet to exist.
if ( -e "$sfile.shp" ) {
#
# Move the existing shapefile to a temp name.
# This is done because the -1 option on shapefileObj open
# only allows for read not append.
# However as (hopefully) shown below this is not hard to
# implement inside mapscript.
system("mv $sfile.shp thistemp.shp; mv $sfile.shx thistemp.shx; touch thistemp.dbf");
#
# Open the existing file.
$ecounts = new shapefileObj("thistemp",-1);
#
# Create the replacement shapefile.
$tcounts = new shapefileObj("$sfile",$mapscript::MS_SHAPEFILE_POINT);
#
# Create the transfer point object.
my $trnspnt = new pointObj();
#
# Loop through each existing point and recreate it in the new shapefile.
for ($epnt=0; $epnt<$dbfreccnt; $epnt++) {
#
# Get the existing point.
$ecounts->getPoint($epnt,$trnspnt);
#
# Put the point into the new shapefile.
$tcounts->addPoint($trnspnt);
}
}
else {
#
# Create the new file.
$tcounts = new shapefileObj("$sfile",$mapscript::MS_SHAPEFILE_POINT);
}
#
# Create the point object for insertion into the shapefile.
my $pnt = new pointObj();
#
# Loop through each record.
# [0]=dontno1, [1]=tablenamechars, [2]=tablename, [3]=dontno2, [4]=#offields
for ($recrd=1; $recrd<$numrecs; $recrd++) {
#
# Create the array for tracking field offsets.
# To be used later while creating dbf records for each gps point.
my @offsets = ();
#
# Set the initial value for unpacking table name.
$unpackstr = "a38 n";
@recordinfo = unpack($unpackstr, $records[$recrd]->{data});
#
# The character count returned is actual + 1.
$recordinfo[1] = $recordinfo[1] - 1;
#
# Unpack the dontno1 and the table name length.
@recordinfo = unpack($unpackstr, $records[$recrd]->{data});
#
# The character count returned is actual + 1.
$recordinfo[1] = $recordinfo[1] - 1;
#
# Add the remainder of the record info to the unpack string
# (name, dontno2, #offields).
$unpackstr = $unpackstr . " a$recordinfo[1] a3 n";
#
# Add the first 10 parts of the first field info to the unpack string.
# All fields appear to have these in common even if they are blank
# for fields that do not use the particular part.
# (fieldname, dontno3, datatype, isrange, null, upperlimit, lowerlimit,
# step, dontno4, fieldsize).
# [offset+0]=fieldname, [offset+1]=dontno3, [offset+2]=datatype
# [offset+3]=isrange, [offset+4]=null,
# [offset+5]=uppperlimit, [offset+6]=lowerlimit, [offset+7]=step,
# [offset+8]=dontno4, [offset+9]=fieldsize
$unpackstr = $unpackstr . " A19 a10 n n a N N N a14 n";
#
# Set the inital value for the field offset
# (the number of parts for the previous field(s)).
my $fieldoffset = 0;
#
# Grab the rest of the record info and
# the field info up to the data length indicator.
($recordinfo[0], $recordinfo[1], $recordinfo[2], $recordinfo[3], $recordinfo[4], @fieldinfo) = unpack $unpackstr, $records[$recrd]->{data};
#
# The character count returned is actual + 1.
$recordinfo[1] = $recordinfo[1] - 1;
#
#
# Print the record info to see if we got this right.
#print "\nRecord # = $recrd\nNumber of Characters in Table Name = $recordinfo[1]\nTable Name = $recordinfo[2]\nNumber of Fields = $recordinfo[4]\n";
# Loop through each field.
for ($fld=0; $fld<$recordinfo[4]; $fld++) {
#
# The actual field number to print is fld + 1.
my $fldprint = $fld + 1;
#
# Grab the field info up to the data length indicator.
($recordinfo[0], $recordinfo[1], $recordinfo[2], $recordinfo[3], $recordinfo[4], @fieldinfo) = unpack $unpackstr, $records[$recrd]->{data};
#
# The character count returned is actual + 1.
$recordinfo[1] = $recordinfo[1] - 1;
#
# What is the length of the data.
my $dlength = $fieldinfo[$fieldoffset+9];
#
# The field type comes in strange sometimes so this should truncate it
# so it only contains values of 0-10.
$fieldinfo[$fieldoffset+2] = 256 * ( ( $fieldinfo[$fieldoffset+2] / 256 ) - ( int( $fieldinfo[$fieldoffset+2] / 256 ) ) );
#
# Okay, the same thing happens with the range.
$fieldinfo[$fieldoffset+3] = 256 * ( ( $fieldinfo[$fieldoffset+3] / 256 ) - ( int( $fieldinfo[$fieldoffset+3] / 256 ) ) );
#
# Add to the unpack string the unpack string for the field type.
$unpackstr = $unpackstr . $ustring[$fieldinfo[$fieldoffset+2]];
#
# For some reason the type appears to be 8-bit instead if 16. So
# to make sure
#
# Add to the array the rest of the parts for the field.
($recordinfo[0], $recordinfo[1], $recordinfo[2], $recordinfo[3], $recordinfo[4], @fieldinfo) = unpack $unpackstr, $records[$recrd]->{data};
#
# Escape out any unprintable characters in the field name.
$fieldinfo[$offsets[$iname]+0] = uc($fieldinfo[$offsets[$iname]+0]);
$fieldinfo[$offsets[$iname]+0] =~ s/[^\x41-\x5A]//g;
#
# The field type comes in strange sometimes so this should truncate it
# so it only contains values of 0-10.
# (binary/unpack guru applications now being accepted).
# Note: Basically this divides by base 16 to move the number 2 decimal
# places left then truncates the whole number and multiplies by
# base 16 to move the decimal 2 places right.
$fieldinfo[$fieldoffset+2] = 256 * ( ( $fieldinfo[$fieldoffset+2] / 256 ) - ( int( $fieldinfo[$fieldoffset+2] / 256 ) ) );
#
# Okay, the same thing happens with the range.
$fieldinfo[$fieldoffset+3] = 256 * ( ( $fieldinfo[$fieldoffset+3] / 256 ) - ( int( $fieldinfo[$fieldoffset+3] / 256 ) ) );
#
# Print the field info to see if we got this right.
#print "Field Offset = $fieldoffset\nField $fldprint Name = $fieldinfo[$fieldoffset+0]\nData Type = $fieldinfo[$fieldoffset+2]\nIsRange = $fieldinfo[$fieldoffset+3]\nUpper Limit = $fieldinfo[$fieldoffset+5]\nLower Limit = $fieldinfo[$fieldoffset+6]\nStep = $fieldinfo[$fieldoffset+7]\nField Size = $fieldinfo[$fieldoffset+9]\n";
#
# How many data parts are there.
# The total number of field parts - 10 is the number of data parts.
my $dparts = $parts[$fieldinfo[$fieldoffset+2]];
#
# Loop through each of the field value parts.
for ($dpart=10; $dpart<$dparts; $dpart++) {
#
# The actual data part id is the current dpart - 9 (0 thru 9 of the
# field array).
my $dprint = $dpart - 9;
#
# Print the field info to see if we got this right.
#print "Data Value $dprint = $fieldinfo[$fieldoffset+$dpart]\n";
}
#
# If the field is a location convert the lat/long to state plane.
if ( $fieldinfo[$fieldoffset+2] == 4 ) {
#
# Do the convert.
# Bunches of notes: The projection name is latlong but supply
# the coordinates as long/lat.
# The +to section contains units of us-ft but MUST specify
# false_east(x_0) in meters.
# An indespensible resource was:
# http://www.edc.uri.edu/nrs/classes/NRS522/Tools/StatePlaneZones.htm
# Note: If I was smart I would have used the pointObj project method.
system("echo \'$fieldinfo[$fieldoffset+18]d$fieldinfo[$fieldoffset+20].$fieldinfo[$fieldoffset+22]W $fieldinfo[$fieldoffset+11]d$fieldinfo[$fieldoffset+13].$fieldinfo[$fieldoffset+15]N\' | cs2cs +proj=latlong +datum=NAD83 +to +proj=lcc +datum=NAD27 +units=ft +lon_0=-82.5 +lat_0=39.666666667 +lat_1=40.433333333 +lat_2=41.433333333 +x_0=609601.21920 +y_0=0 > /tmp/coordinates");
#
# Open the coordinate file.
open(COORDS,"</tmp/coordinates");
#
# Read the coordinates in.
my @coords = split('\t', <COORDS>);
my @northelev = split(' ',$coords[1]);
#
# Print out the coordinates to see if we have this right.
#print "Easting = $coords[0], Northing = $northelev[0], Elevation = $fieldinfo[$fieldoffset+16]\n";
#
# Close the coordinate file.
close COORDS;
#
# Set the x & y for the point object.
$pnt->{x} = $coords[0];
$pnt->{y} = $northelev[0];
#
# Add the point to the shapefile.
$tcounts->addPoint($pnt);
}
#
# Print the unpack string to see if we got this right.
#print "UnPack String = $unpackstr\n";
#
# Add the next fields standard 10 parts to the unpack string.
$unpackstr = $unpackstr . " A19 a10 n n a N N N a14 n";
#
# Record where this field started at.
$offsets[$fld] = $fieldoffset;
#
# Set the field offset to include the now completed field.
$fieldoffset = $fieldoffset + $parts[$fieldinfo[$fieldoffset+2]];
}
#
# Does the dbf need created and is this the first record.
if ( ( $dbfreccnt == -1 ) && ( $recrd == 1 ) ) {
#
# Set the record count to 0.
$dbfreccnt = 0;
#
# How many fields are there.
my $fldcnt = scalar(@offsets);
#
# Initialize the field names, type, length, & decimal strings to blank.
my $fldnames = '';
my $fldtypes = '';
my $fldlenth = '';
my $flddecml = '';
#
# Loop through each field and concatenate the name, type, length, & decimal together.
for ($iname=0; $iname<$fldcnt; $iname++) {
#
# Escape out any unprintable characters in the field name.
$fieldinfo[$offsets[$iname]+0] = uc($fieldinfo[$offsets[$iname]+0]);
$fieldinfo[$offsets[$iname]+0] =~ s/[^\x41-\x5A]//g;
#
# The field type comes in strange sometimes so this should truncate it
# so it only contains values of 0-10.
# (binary/unpack guru applications now being accepted).
# Note: Basically this divides by base 16 to move the number 2 decimal
# places left then truncates the whole number and multiplies by
# base 16 to move the decimal 2 places right.
$fieldinfo[$offsets[$iname]+2] = 256 * ( ( $fieldinfo[$offsets[$iname]+2] / 256 ) - ( int( $fieldinfo[$offsets[$iname]+2] / 256 ) ) );
#
# Okay, the same thing happens with the range.
$fieldinfo[$offsets[$iname]+3] = 256 * ( ( $fieldinfo[$offsets[$iname]+3] / 256 ) - ( int( $fieldinfo[$offsets[$iname]+3] / 256 ) ) );
#
# Concatenate the field name.
$fldnames = $fldnames . ' "' . $fieldinfo[$offsets[$iname]+0] . '"';
#
# Concatenate the field types.
$fldtypes = $fldtypes . ' "' . $dbfftype[$fieldinfo[$offsets[$iname]+2]] . '"';
#
# Concatenate the field lengths.
$fldlenth = $fldlenth . ' "' . $dbffsize[$fieldinfo[$offsets[$iname]+2]] . '"';
#
# Concatenate the field decimals.
# All undef right now.
$flddecml = $flddecml . ' "undef"';
#
# If this is not the last field throw in a comma.
if ( $iname != ( $fldcnt - 1 ) ) {
$fldnames = $fldnames . ',';
$fldtypes = $fldtypes . ',';
$fldlenth = $fldlenth . ',';
$flddecml = $flddecml . ',';
}
}
#
# Add the fields for the record number and error flag.
$fldnames = $fldnames . ', "RECORD", "ERRFLAG"';
$fldtypes = $fldtypes . ', "N", "N"';
$fldlenth = $fldlenth . ', "6", "2"';
$flddecml = $flddecml . ', "undef", "undef"';
#
# Create the xbase call.
my $xbcall = 'XBase->create(name => "' . $sfile . '.dbf", field_names => [' . $fldnames . ' ], field_types => [' . $fldtypes . ' ], field_lengths => [' . $fldlenth . ' ], field_decimals => [' . $flddecml . ' ]) or die XBase->errstr;';
#
# Print out the create line to see if we got this right.
#print "Field Names = $fldnames\nField Types = $fldtypes\nField Sizes = $fldlenth\nField Decimals = $flddecml\n";
#
# Create the dbf file.
$dbh = eval($xbcall);
}
#
# Add the data for this pdb record to the dbf file.
#
# Start the xbase add-record call.
my $xbadd = '$dbh->set_record($dbfreccnt,';
#
# How many fields are there.
my $fldcnt = scalar(@offsets);
#
# Go through each field and concatenate the values together.
for ($iname=0; $iname<$fldcnt; $iname++) {
#
# The field type comes in strange sometimes so this should truncate it
# so it only contains values of 0-10.
# (binary/unpack guru applications now being accepted).
# Note: Basically this divides by base 16 to move the number 2 decimal
# places left then truncates the whole number and multiplies by
# base 16 to move the decimal 2 places right.
$fieldinfo[$offsets[$iname]+2] = 256 * ( ( $fieldinfo[$offsets[$iname]+2] / 256 ) - ( int( $fieldinfo[$offsets[$iname]+2] / 256 ) ) );
#
# Is this a number type record.
if ( $fieldinfo[$offsets[$iname]+2] == 7 ) {
$xbadd = $xbadd . $fieldinfo[$offsets[$iname]+10];
}
#
# Is this a location type record.
if ( $fieldinfo[$offsets[$iname]+2] == 4 ) {
$xbadd = $xbadd . '"' . $fieldinfo[$offsets[$iname]+18] . 'd' . $fieldinfo[$offsets[$iname]+20] . '.' . $fieldinfo[$offsets[$iname]+22] . 'W,' . $fieldinfo[$offsets[$iname]+11] . 'd' . $fieldinfo[$offsets[$iname]+13] . '.' . $fieldinfo[$offsets[$iname]+15] . 'N,' . $fieldinfo[$offsets[$iname]+16] . '"';
}
#
# If this is not the last field throw in a comma.
if ( $iname != ( $fldcnt - 1 ) ) {
$xbadd = $xbadd . ',';
}
}
#
# Add the closer to the end of the xbase add-record call.
$xbadd = $xbadd . ', ' . $dbfreccnt . ', 0);';
#
# Print the xbase add-record line to see if we got this right.
#print "$xbadd\n";
#
# Add the record to the dbf file.
eval($xbadd);
#
# Increment the record counter.
$dbfreccnt = $dbfreccnt + 1;
}
#
# Close the new shapefile.
undef $tcounts;
#
# Close the dbf handle/file.
undef $dbh;
#
# Get rid of temporary shapefiles if needed.
if ( -e "thistemp.shp" ) {
unlink "thistemp.shp";
unlink "thistemp.shx";
unlink "thistemp.dbf";
}
back to PerlMapScrip