package shp import ( "encoding/binary" "io" "strings" ) //go:generate stringer -type=ShapeType // ShapeType is a identifier for the the type of shapes. type ShapeType int32 // These are the possible shape types. const ( NULL ShapeType = 0 POINT ShapeType = 1 POLYLINE ShapeType = 3 POLYGON ShapeType = 5 MULTIPOINT ShapeType = 8 POINTZ ShapeType = 11 POLYLINEZ ShapeType = 13 POLYGONZ ShapeType = 15 MULTIPOINTZ ShapeType = 18 POINTM ShapeType = 21 POLYLINEM ShapeType = 23 POLYGONM ShapeType = 25 MULTIPOINTM ShapeType = 28 MULTIPATCH ShapeType = 31 ) // Box structure made up from four coordinates. This type // is used to represent bounding boxes type Box struct { MinX, MinY, MaxX, MaxY float64 } // Extend extends the box with coordinates from the provided // box. This method calls Box.ExtendWithPoint twice with // {MinX, MinY} and {MaxX, MaxY} func (b *Box) Extend(box Box) { b.ExtendWithPoint(Point{box.MinX, box.MinY}) b.ExtendWithPoint(Point{box.MaxX, box.MaxY}) } // ExtendWithPoint extends box with coordinates from point // if they are outside the range of the current box. func (b *Box) ExtendWithPoint(p Point) { if p.X < b.MinX { b.MinX = p.X } if p.Y < b.MinY { b.MinY = p.Y } if p.X > b.MaxX { b.MaxX = p.X } if p.Y > b.MaxY { b.MaxY = p.Y } } // BBoxFromPoints returns the bounding box calculated // from points. func BBoxFromPoints(points []Point) (box Box) { for k, p := range points { if k == 0 { box = Box{p.X, p.Y, p.X, p.Y} } else { box.ExtendWithPoint(p) } } return } // Shape interface type Shape interface { BBox() Box read(io.Reader) write(io.Writer) } // Null is an empty shape. type Null struct { } // BBox Returns an empty BBox at the geometry origin. func (n Null) BBox() Box { return Box{0.0, 0.0, 0.0, 0.0} } func (n *Null) read(file io.Reader) { binary.Read(file, binary.LittleEndian, n) } func (n *Null) write(file io.Writer) { binary.Write(file, binary.LittleEndian, n) } // Point is the shape that consists of single a geometry point. type Point struct { X, Y float64 } // BBox returns the bounding box of the Point feature, i.e. an empty area at // the point location itself. func (p Point) BBox() Box { return Box{p.X, p.Y, p.X, p.Y} } func (p *Point) read(file io.Reader) { binary.Read(file, binary.LittleEndian, p) } func (p *Point) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p) } func flatten(points [][]Point) []Point { n, i := 0, 0 for _, v := range points { n += len(v) } r := make([]Point, n) for _, v := range points { for _, p := range v { r[i] = p i++ } } return r } // PolyLine is a shape type that consists of an ordered set of vertices that // consists of one or more parts. A part is a connected sequence of two ore // more points. Parts may or may not be connected to another and may or may not // intersect each other. type PolyLine struct { Box NumParts int32 NumPoints int32 Parts []int32 Points []Point } // NewPolyLine returns a pointer a new PolyLine created // with the provided points. The inner slice should be // the points that the parent part consists of. func NewPolyLine(parts [][]Point) *PolyLine { points := flatten(parts) p := &PolyLine{} p.NumParts = int32(len(parts)) p.NumPoints = int32(len(points)) p.Parts = make([]int32, len(parts)) var marker int32 for i, part := range parts { p.Parts[i] = marker marker += int32(len(part)) } p.Points = points p.Box = p.BBox() return p } // BBox returns the bounding box of the PolyLine feature func (p PolyLine) BBox() Box { return BBoxFromPoints(p.Points) } func (p *PolyLine) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumParts) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Parts = make([]int32, p.NumParts) p.Points = make([]Point, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Parts) binary.Read(file, binary.LittleEndian, &p.Points) } func (p *PolyLine) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumParts) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Parts) binary.Write(file, binary.LittleEndian, p.Points) } // Polygon is identical to the PolyLine struct. However the parts must form // rings that may not intersect. type Polygon PolyLine // BBox returns the bounding box of the Polygon feature func (p Polygon) BBox() Box { return BBoxFromPoints(p.Points) } func (p *Polygon) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumParts) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Parts = make([]int32, p.NumParts) p.Points = make([]Point, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Parts) binary.Read(file, binary.LittleEndian, &p.Points) } func (p *Polygon) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumParts) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Parts) binary.Write(file, binary.LittleEndian, p.Points) } // MultiPoint is the shape that consists of multiple points. type MultiPoint struct { Box Box NumPoints int32 Points []Point } // BBox returns the bounding box of the MultiPoint feature func (p MultiPoint) BBox() Box { return BBoxFromPoints(p.Points) } func (p *MultiPoint) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Points = make([]Point, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Points) } func (p *MultiPoint) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Points) } // PointZ is a triplet of double precision coordinates plus a measure. type PointZ struct { X float64 Y float64 Z float64 M float64 } // BBox eturns the bounding box of the PointZ feature which is an zero-sized area // at the X and Y coordinates of the feature. func (p PointZ) BBox() Box { return Box{p.X, p.Y, p.X, p.Y} } func (p *PointZ) read(file io.Reader) { binary.Read(file, binary.LittleEndian, p) } func (p *PointZ) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p) } // PolyLineZ is a shape which consists of one or more parts. A part is a // connected sequence of two or more points. Parts may or may not be connected // and may or may not intersect one another. type PolyLineZ struct { Box Box NumParts int32 NumPoints int32 Parts []int32 Points []Point ZRange [2]float64 ZArray []float64 MRange [2]float64 MArray []float64 } // BBox eturns the bounding box of the PolyLineZ feature. func (p PolyLineZ) BBox() Box { return BBoxFromPoints(p.Points) } func (p *PolyLineZ) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumParts) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Parts = make([]int32, p.NumParts) p.Points = make([]Point, p.NumPoints) p.ZArray = make([]float64, p.NumPoints) p.MArray = make([]float64, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Parts) binary.Read(file, binary.LittleEndian, &p.Points) binary.Read(file, binary.LittleEndian, &p.ZRange) binary.Read(file, binary.LittleEndian, &p.ZArray) binary.Read(file, binary.LittleEndian, &p.MRange) binary.Read(file, binary.LittleEndian, &p.MArray) } func (p *PolyLineZ) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumParts) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Parts) binary.Write(file, binary.LittleEndian, p.Points) binary.Write(file, binary.LittleEndian, p.ZRange) binary.Write(file, binary.LittleEndian, p.ZArray) binary.Write(file, binary.LittleEndian, p.MRange) binary.Write(file, binary.LittleEndian, p.MArray) } // PolygonZ structure is identical to the PolyLineZ structure. type PolygonZ PolyLineZ // BBox returns the bounding box of the PolygonZ feature func (p PolygonZ) BBox() Box { return BBoxFromPoints(p.Points) } func (p *PolygonZ) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumParts) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Parts = make([]int32, p.NumParts) p.Points = make([]Point, p.NumPoints) p.ZArray = make([]float64, p.NumPoints) p.MArray = make([]float64, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Parts) binary.Read(file, binary.LittleEndian, &p.Points) binary.Read(file, binary.LittleEndian, &p.ZRange) binary.Read(file, binary.LittleEndian, &p.ZArray) binary.Read(file, binary.LittleEndian, &p.MRange) binary.Read(file, binary.LittleEndian, &p.MArray) } func (p *PolygonZ) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumParts) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Parts) binary.Write(file, binary.LittleEndian, p.Points) binary.Write(file, binary.LittleEndian, p.ZRange) binary.Write(file, binary.LittleEndian, p.ZArray) binary.Write(file, binary.LittleEndian, p.MRange) binary.Write(file, binary.LittleEndian, p.MArray) } // MultiPointZ consists of one ore more PointZ. type MultiPointZ struct { Box Box NumPoints int32 Points []Point ZRange [2]float64 ZArray []float64 MRange [2]float64 MArray []float64 } // BBox eturns the bounding box of the MultiPointZ feature. func (p MultiPointZ) BBox() Box { return BBoxFromPoints(p.Points) } func (p *MultiPointZ) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Points = make([]Point, p.NumPoints) p.ZArray = make([]float64, p.NumPoints) p.MArray = make([]float64, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Points) binary.Read(file, binary.LittleEndian, &p.ZRange) binary.Read(file, binary.LittleEndian, &p.ZArray) binary.Read(file, binary.LittleEndian, &p.MRange) binary.Read(file, binary.LittleEndian, &p.MArray) } func (p *MultiPointZ) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Points) binary.Write(file, binary.LittleEndian, p.ZRange) binary.Write(file, binary.LittleEndian, p.ZArray) binary.Write(file, binary.LittleEndian, p.MRange) binary.Write(file, binary.LittleEndian, p.MArray) } // PointM is a point with a measure. type PointM struct { X float64 Y float64 M float64 } // BBox returns the bounding box of the PointM feature which is a zero-sized // area at the X- and Y-coordinates of the point. func (p PointM) BBox() Box { return Box{p.X, p.Y, p.X, p.Y} } func (p *PointM) read(file io.Reader) { binary.Read(file, binary.LittleEndian, p) } func (p *PointM) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p) } // PolyLineM is the polyline in which each point also has a measure. type PolyLineM struct { Box Box NumParts int32 NumPoints int32 Parts []int32 Points []Point MRange [2]float64 MArray []float64 } // BBox returns the bounding box of the PolyLineM feature. func (p PolyLineM) BBox() Box { return BBoxFromPoints(p.Points) } func (p *PolyLineM) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumParts) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Parts = make([]int32, p.NumParts) p.Points = make([]Point, p.NumPoints) p.MArray = make([]float64, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Parts) binary.Read(file, binary.LittleEndian, &p.Points) binary.Read(file, binary.LittleEndian, &p.MRange) binary.Read(file, binary.LittleEndian, &p.MArray) } func (p *PolyLineM) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumParts) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Parts) binary.Write(file, binary.LittleEndian, p.Points) binary.Write(file, binary.LittleEndian, p.MRange) binary.Write(file, binary.LittleEndian, p.MArray) } // PolygonM structure is identical to the PolyLineZ structure. type PolygonM PolyLineZ // BBox returns the bounding box of the PolygonM feature. func (p PolygonM) BBox() Box { return BBoxFromPoints(p.Points) } func (p *PolygonM) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumParts) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Parts = make([]int32, p.NumParts) p.Points = make([]Point, p.NumPoints) p.MArray = make([]float64, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Parts) binary.Read(file, binary.LittleEndian, &p.Points) binary.Read(file, binary.LittleEndian, &p.MRange) binary.Read(file, binary.LittleEndian, &p.MArray) } func (p *PolygonM) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumParts) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Parts) binary.Write(file, binary.LittleEndian, p.Points) binary.Write(file, binary.LittleEndian, p.MRange) binary.Write(file, binary.LittleEndian, p.MArray) } // MultiPointM is the collection of multiple points with measures. type MultiPointM struct { Box Box NumPoints int32 Points []Point MRange [2]float64 MArray []float64 } // BBox eturns the bounding box of the MultiPointM feature func (p MultiPointM) BBox() Box { return BBoxFromPoints(p.Points) } func (p *MultiPointM) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Points = make([]Point, p.NumPoints) p.MArray = make([]float64, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Points) binary.Read(file, binary.LittleEndian, &p.MRange) binary.Read(file, binary.LittleEndian, &p.MArray) } func (p *MultiPointM) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Points) binary.Write(file, binary.LittleEndian, p.MRange) binary.Write(file, binary.LittleEndian, p.MArray) } // MultiPatch consists of a number of surfaces patches. Each surface path // descries a surface. The surface patches of a MultiPatch are referred to as // its parts, and the type of part controls how the order of vertices of an // MultiPatch part is interpreted. type MultiPatch struct { Box Box NumParts int32 NumPoints int32 Parts []int32 PartTypes []int32 Points []Point ZRange [2]float64 ZArray []float64 MRange [2]float64 MArray []float64 } // BBox returns the bounding box of the MultiPatch feature func (p MultiPatch) BBox() Box { return BBoxFromPoints(p.Points) } func (p *MultiPatch) read(file io.Reader) { binary.Read(file, binary.LittleEndian, &p.Box) binary.Read(file, binary.LittleEndian, &p.NumParts) binary.Read(file, binary.LittleEndian, &p.NumPoints) p.Parts = make([]int32, p.NumParts) p.PartTypes = make([]int32, p.NumParts) p.Points = make([]Point, p.NumPoints) p.ZArray = make([]float64, p.NumPoints) p.MArray = make([]float64, p.NumPoints) binary.Read(file, binary.LittleEndian, &p.Parts) binary.Read(file, binary.LittleEndian, &p.PartTypes) binary.Read(file, binary.LittleEndian, &p.Points) binary.Read(file, binary.LittleEndian, &p.ZRange) binary.Read(file, binary.LittleEndian, &p.ZArray) binary.Read(file, binary.LittleEndian, &p.MRange) binary.Read(file, binary.LittleEndian, &p.MArray) } func (p *MultiPatch) write(file io.Writer) { binary.Write(file, binary.LittleEndian, p.Box) binary.Write(file, binary.LittleEndian, p.NumParts) binary.Write(file, binary.LittleEndian, p.NumPoints) binary.Write(file, binary.LittleEndian, p.Parts) binary.Write(file, binary.LittleEndian, p.PartTypes) binary.Write(file, binary.LittleEndian, p.Points) binary.Write(file, binary.LittleEndian, p.ZRange) binary.Write(file, binary.LittleEndian, p.ZArray) binary.Write(file, binary.LittleEndian, p.MRange) binary.Write(file, binary.LittleEndian, p.MArray) } // Field representation of a field object in the DBF file type Field struct { Name [11]byte Fieldtype byte Addr [4]byte // not used Size uint8 Precision uint8 Padding [14]byte } // Returns a string representation of the Field. Currently // this only returns field name. func (f Field) String() string { return strings.TrimRight(string(f.Name[:]), "\x00") } // StringField returns a Field that can be used in SetFields to initialize the // DBF file. func StringField(name string, length uint8) Field { // TODO: Error checking field := Field{Fieldtype: 'C', Size: length} copy(field.Name[:], []byte(name)) return field } // NumberField returns a Field that can be used in SetFields to initialize the // DBF file. func NumberField(name string, length uint8) Field { field := Field{Fieldtype: 'N', Size: length} copy(field.Name[:], []byte(name)) return field } // FloatField returns a Field that can be used in SetFields to initialize the // DBF file. Used to store floating points with precision in the DBF. func FloatField(name string, length uint8, precision uint8) Field { field := Field{Fieldtype: 'F', Size: length, Precision: precision} copy(field.Name[:], []byte(name)) return field } // DateField feturns a Field that can be used in SetFields to initialize the // DBF file. Used to store Date strings formatted as YYYYMMDD. Data wise this // is the same as a StringField with length 8. func DateField(name string) Field { field := Field{Fieldtype: 'D', Size: 8} copy(field.Name[:], []byte(name)) return field }