254 lines
6.2 KiB
Go
254 lines
6.2 KiB
Go
package shp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Reader provides a interface for reading Shapefiles. Calls
|
|
// to the Next method will iterate through the objects in the
|
|
// Shapefile. After a call to Next the object will be available
|
|
// through the Shape method.
|
|
type Reader struct {
|
|
GeometryType ShapeType
|
|
bbox Box
|
|
err error
|
|
|
|
shp readSeekCloser
|
|
shape Shape
|
|
num int32
|
|
filename string
|
|
filelength int64
|
|
|
|
dbf readSeekCloser
|
|
dbfFields []Field
|
|
dbfNumRecords int32
|
|
dbfHeaderLength int16
|
|
dbfRecordLength int16
|
|
}
|
|
|
|
type readSeekCloser interface {
|
|
io.Reader
|
|
io.Seeker
|
|
io.Closer
|
|
}
|
|
|
|
// Open opens a Shapefile for reading.
|
|
func Open(filename string) (*Reader, error) {
|
|
ext := filepath.Ext(filename)
|
|
if strings.ToLower(ext) != ".shp" {
|
|
return nil, fmt.Errorf("Invalid file extension: %s", filename)
|
|
}
|
|
shp, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s := &Reader{filename: strings.TrimSuffix(filename, ext), shp: shp}
|
|
return s, s.readHeaders()
|
|
}
|
|
|
|
// BBox returns the bounding box of the shapefile.
|
|
func (r *Reader) BBox() Box {
|
|
return r.bbox
|
|
}
|
|
|
|
// Read and parse headers in the Shapefile. This will
|
|
// fill out GeometryType, filelength and bbox.
|
|
func (r *Reader) readHeaders() error {
|
|
er := &errReader{Reader: r.shp}
|
|
// don't trust the the filelength in the header
|
|
r.filelength, _ = r.shp.Seek(0, io.SeekEnd)
|
|
|
|
var filelength int32
|
|
r.shp.Seek(24, 0)
|
|
// file length
|
|
binary.Read(er, binary.BigEndian, &filelength)
|
|
r.shp.Seek(32, 0)
|
|
binary.Read(er, binary.LittleEndian, &r.GeometryType)
|
|
r.bbox.MinX = readFloat64(er)
|
|
r.bbox.MinY = readFloat64(er)
|
|
r.bbox.MaxX = readFloat64(er)
|
|
r.bbox.MaxY = readFloat64(er)
|
|
r.shp.Seek(100, 0)
|
|
return er.e
|
|
}
|
|
|
|
func readFloat64(r io.Reader) float64 {
|
|
var bits uint64
|
|
binary.Read(r, binary.LittleEndian, &bits)
|
|
return math.Float64frombits(bits)
|
|
}
|
|
|
|
// Close closes the Shapefile.
|
|
func (r *Reader) Close() error {
|
|
if r.err == nil {
|
|
r.err = r.shp.Close()
|
|
if r.dbf != nil {
|
|
r.dbf.Close()
|
|
}
|
|
}
|
|
return r.err
|
|
}
|
|
|
|
// Shape returns the most recent feature that was read by
|
|
// a call to Next. It returns two values, the int is the
|
|
// object index starting from zero in the shapefile which
|
|
// can be used as row in ReadAttribute, and the Shape is the object.
|
|
func (r *Reader) Shape() (int, Shape) {
|
|
return int(r.num) - 1, r.shape
|
|
}
|
|
|
|
// Attribute returns value of the n-th attribute of the most recent feature
|
|
// that was read by a call to Next.
|
|
func (r *Reader) Attribute(n int) string {
|
|
return r.ReadAttribute(int(r.num)-1, n)
|
|
}
|
|
|
|
// newShape creates a new shape with a given type.
|
|
func newShape(shapetype ShapeType) (Shape, error) {
|
|
switch shapetype {
|
|
case NULL:
|
|
return new(Null), nil
|
|
case POINT:
|
|
return new(Point), nil
|
|
case POLYLINE:
|
|
return new(PolyLine), nil
|
|
case POLYGON:
|
|
return new(Polygon), nil
|
|
case MULTIPOINT:
|
|
return new(MultiPoint), nil
|
|
case POINTZ:
|
|
return new(PointZ), nil
|
|
case POLYLINEZ:
|
|
return new(PolyLineZ), nil
|
|
case POLYGONZ:
|
|
return new(PolygonZ), nil
|
|
case MULTIPOINTZ:
|
|
return new(MultiPointZ), nil
|
|
case POINTM:
|
|
return new(PointM), nil
|
|
case POLYLINEM:
|
|
return new(PolyLineM), nil
|
|
case POLYGONM:
|
|
return new(PolygonM), nil
|
|
case MULTIPOINTM:
|
|
return new(MultiPointM), nil
|
|
case MULTIPATCH:
|
|
return new(MultiPatch), nil
|
|
default:
|
|
return nil, fmt.Errorf("Unsupported shape type: %v", shapetype)
|
|
}
|
|
}
|
|
|
|
// Next reads in the next Shape in the Shapefile, which
|
|
// will then be available through the Shape method. It
|
|
// returns false when the reader has reached the end of the
|
|
// file or encounters an error.
|
|
func (r *Reader) Next() bool {
|
|
cur, _ := r.shp.Seek(0, io.SeekCurrent)
|
|
if cur >= r.filelength {
|
|
return false
|
|
}
|
|
|
|
var size int32
|
|
var shapetype ShapeType
|
|
er := &errReader{Reader: r.shp}
|
|
binary.Read(er, binary.BigEndian, &r.num)
|
|
binary.Read(er, binary.BigEndian, &size)
|
|
binary.Read(er, binary.LittleEndian, &shapetype)
|
|
if er.e != nil {
|
|
if er.e != io.EOF {
|
|
r.err = fmt.Errorf("Error when reading metadata of next shape: %v", er.e)
|
|
} else {
|
|
r.err = io.EOF
|
|
}
|
|
return false
|
|
}
|
|
|
|
var err error
|
|
r.shape, err = newShape(shapetype)
|
|
if err != nil {
|
|
r.err = fmt.Errorf("Error decoding shape type: %v", err)
|
|
return false
|
|
}
|
|
r.shape.read(er)
|
|
if er.e != nil {
|
|
r.err = fmt.Errorf("Error while reading next shape: %v", er.e)
|
|
return false
|
|
}
|
|
|
|
// move to next object
|
|
r.shp.Seek(int64(size)*2+cur+8, 0)
|
|
return true
|
|
}
|
|
|
|
// Opens DBF file using r.filename + "dbf". This method
|
|
// will parse the header and fill out all dbf* values int
|
|
// the f object.
|
|
func (r *Reader) openDbf() (err error) {
|
|
if r.dbf != nil {
|
|
return
|
|
}
|
|
|
|
r.dbf, err = os.Open(r.filename + ".dbf")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// read header
|
|
r.dbf.Seek(4, io.SeekStart)
|
|
binary.Read(r.dbf, binary.LittleEndian, &r.dbfNumRecords)
|
|
binary.Read(r.dbf, binary.LittleEndian, &r.dbfHeaderLength)
|
|
binary.Read(r.dbf, binary.LittleEndian, &r.dbfRecordLength)
|
|
|
|
r.dbf.Seek(20, io.SeekCurrent) // skip padding
|
|
numFields := int(math.Floor(float64(r.dbfHeaderLength-33) / 32.0))
|
|
r.dbfFields = make([]Field, numFields)
|
|
binary.Read(r.dbf, binary.LittleEndian, &r.dbfFields)
|
|
return
|
|
}
|
|
|
|
// Fields returns a slice of Fields that are present in the
|
|
// DBF table.
|
|
func (r *Reader) Fields() []Field {
|
|
err := r.openDbf()
|
|
fmt.Println(err)
|
|
if err != nil {
|
|
return nil
|
|
} // make sure we have dbf file to read from
|
|
return r.dbfFields
|
|
}
|
|
|
|
// Err returns the last non-EOF error encountered.
|
|
func (r *Reader) Err() error {
|
|
if r.err == io.EOF {
|
|
return nil
|
|
}
|
|
return r.err
|
|
}
|
|
|
|
// AttributeCount returns number of records in the DBF table.
|
|
func (r *Reader) AttributeCount() int {
|
|
r.openDbf() // make sure we have a dbf file to read from
|
|
return int(r.dbfNumRecords)
|
|
}
|
|
|
|
// ReadAttribute returns the attribute value at row for field in
|
|
// the DBF table as a string. Both values starts at 0.
|
|
func (r *Reader) ReadAttribute(row int, field int) string {
|
|
r.openDbf() // make sure we have a dbf file to read from
|
|
seekTo := 1 + int64(r.dbfHeaderLength) + (int64(row) * int64(r.dbfRecordLength))
|
|
for n := 0; n < field; n++ {
|
|
seekTo += int64(r.dbfFields[n].Size)
|
|
}
|
|
r.dbf.Seek(seekTo, io.SeekStart)
|
|
buf := make([]byte, r.dbfFields[field].Size)
|
|
r.dbf.Read(buf)
|
|
return strings.Trim(string(buf[:]), " ")
|
|
}
|