ShapeFile loading script

I wrote this script to load a shapefile (which only contained PolygonZ’s) into blender. Turns out I might not actually want to be using shapefile files … so I’m unlikely to take this script any further.

As such I thought I’d post it here in case someone else has a need to load shapefile as it might save them a few hours.

It should be reasonably easy to extend to cover other shapefile types; hope its of use to someone

Rich


import struct
import StringIO
import string



class ShapeFile_Point:
	def __init__( self, itr ):
		self.x = 0.0
		self.y = 0.0		
		
		self.x, self.y = struct.unpack( "<dd", itr.read( 16 ) )		
		self.x = float( self.x )
		self.y = float( self.y )
	


class ShapeFile_PolygonZ:
	def __init__( self, itr ):
		self.box = []
		self.parts = []
		self.points = []
		self.zMin = 0.0
		self.zMax = 0.0
		self.zArray = []
		self.mMin = 0.0
		self.mMax = 0.0
		self.mArray = []		
		
		self.box = struct.unpack( "<dddd", itr.read( 32 ) )
		numParts, numPoints = struct.unpack( "<ii", itr.read( 8 ) )
		for i in xrange( 0, numParts ):
			self.parts.append( struct.unpack( "<i", itr.read( 4 ) )[ 0 ] )

		for i in xrange( 0, numPoints ):
			self.points.append( ShapeFile_Point( itr ) )
	
		self.zMin = struct.unpack( "<d", itr.read( 8 ) )[ 0 ]	
		self.zMax = struct.unpack( "<d", itr.read( 8 ) )[ 0 ]		
		for i in xrange( 0, numPoints ):
			self.zArray.append( struct.unpack( "<d", itr.read( 8 ) )[ 0 ] )

		data = itr.read( 8 )
		if len( data ) != 0:
			self.mMin = struct.unpack( "<d", data )[ 0 ]	
			self.mMax = struct.unpack( "<d", itr.read( 8 ) )[ 0 ]		
			for i in xrange( 0, numPoints ):
				self.mArray.append( struct.unpack( "<d", itr.read( 8 ) )[ 0 ] )
		
		retVal  = "      box: %s
" % ( str( self.box ) )
		retVal += "      noParts: %i
" % ( len( self.parts ) )
		retVal += "      noPoints: %i
" % ( len( self.points ) )
		retVal += "      zMin: %s
" % ( self.zMin )
		retVal += "      zMax: %s
" % ( self.zMax )
		retVal += "      mMin: %s
" % ( self.mMin )
		retVal += "      mMax: %s
" % ( self.mMax ) 
		print retVal		
		
		
	def __str__( self ):
		retVal  = "      box: %s
" % ( str( self.box ) )
		retVal += "      noParts: %i
" % ( len( self.parts ) )
		retVal += "      noPoints: %i
" % ( len( self.points ) )
		retVal += "      zMin: %s
" % ( self.zMin )
		retVal += "      zMax: %s
" % ( self.zMax )
		retVal += "      mMin: %s
" % ( self.mMin )
		retVal += "      mMax: %s
" % ( self.mMax ) 								
		return retVal
	


class ShapeFile_Record:
	def __init__( self, itr ):
		self.shapeType = 0
		self.data = None

		self.shapeType = struct.unpack( "<i", itr.read( 4 ) )[ 0 ]	

		retVal  = "    shapeType: %s
" % ( self.shapeType )
		print retVal

		if int( self.shapeType ) == 15:
			self.data = ShapeFile_PolygonZ( itr )		


	def __str__( self ):
		retVal  = "    shapeType: %s
" % ( self.shapeType )
		retVal += str( self.data )
		return retVal 



class ShapeFile:
	def __init__( self, fname ):
		self.version = 0
		self.shapeType = 0
		self.boundingBox = []
		self.records = []

		f = open( fname, "rb" )
		
		self._parseFileHeader( f )
		while True:	
			data = f.read( 8 )
			if len( data ) == 0:
				break
	
			rh = struct.unpack( ">ii", data )
			itr = StringIO.StringIO( f.read( int( rh[ 1 ] ) * 2 ) ) # value is size in in16's	
			self.records.append( ShapeFile_Record( itr ) )


	def _parseFileHeader( self, f ):
		self.fileCode = struct.unpack( ">i", f.read( 4 ) )[ 0 ]
		unused1 = struct.unpack( ">iiiii", f.read( 20 ) )
		self.fileLength = struct.unpack( ">i", f.read( 4 ) )[ 0 ]
		self.version, self.shapeType = struct.unpack( "<ii", f.read( 8 ) )
		self.boundingBox = struct.unpack( "<dddddddd", f.read( 64 ) )

		retVal  = "  version: %s
" % ( self.version )
		retVal += "  shapeType: %s
" % ( self.shapeType )
		retVal += "  boundingBox: %s
" % ( str( self.boundingBox ) )
		print retVal		
		
		
	def __str__( self ):
		retVal  = "  version: %s
" % ( self.version )
		retVal += "  shapeType: %s
" % ( self.shapeType )
		retVal += "  boundingBox: %s
" % ( str( self.boundingBox ) )
		for r in self.records:
			retVal += str( r )
		return retVal
	


s = ShapeFile( "e000n52e.shp" )
print s


import bpy
from Blender import Curve, Object, Scene
from Blender import *

for rec in s.recordsa:
	loop = bpy.data.meshes.new()
	for pt in rec.data.points:
		loop.verts.extend( [ ( pt.x, pt.y, 0.0 ) ] )
	
#	#for i in xrange( 0, len( rec.data.parts ) ):
#	#	print "--------------------"
#	#	for j in xrange( rec.data.parts[ i - 1 ] + 1, rec.data.parts[ i ] ):
#	#		print j
#		#	loop.edges.extend( ( loop.verts[ j - 1 ], loop.verts[ j ] ) )
	bpy.data.scenes.active.objects.new( loop )

Hi, Thanks for this - its going to be very useful for a GIS project.

would be cool if someone could port to blender 2.5, nice addon

Ok, worked out how to put in the elevation data, it gets ugly. The elevation is in the dbf … yep dbase database file… Couldn’t find a 3.2 reader so I’ve hacked pydbf to do enough. Here is the contour map produced. Be interested to know if there is a way of skinning this puppy… going to start another thread to see.

There are some options for producing contours http://www.gdal.org/gdal_contour.html . The -3d option produces the PolygonZ type.

Attachments

contour.blend (843 KB)

Wouldn’t be better to use functions directly from Gdal python wrapper.
Thiscould be helpful

Cheers for that… it was originally my intention. When I got this error trying to import ogr


>>> import ogr
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "e:\python32\lib\site-packages\ogr.py", line 2, in <module>
    from osgeo.gdal import deprecation_warn
  File "e:\python32\lib\site-packages\osgeo\__init__.py", line 21, in <module>
    _gdal = swig_import_helper()
  File "e:\python32\lib\site-packages\osgeo\__init__.py", line 17, in swig_impor
t_helper
    _mod = imp.load_module('_gdal', fp, pathname, description)

it went straight to the too long to work out, too hard basket. I only need the output of the tools that are installed by GDAL (gdal_contour.exe on windows for example) to use in blender so I wrote my own wrapper to call them using subprocess from blender. Not ideal I know… but when / if sharing that code all the blender user has to do is point to the GDAL bin directory, which again could be a lot simpler than getting the modules to play with the blender py distbn.