初始
This commit is contained in:
253
api/v1/common/tool/shp/reader.go
Normal file
253
api/v1/common/tool/shp/reader.go
Normal file
@ -0,0 +1,253 @@
|
||||
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[:]), " ")
|
||||
}
|
Reference in New Issue
Block a user