This commit is contained in:
2025-07-07 20:11:59 +08:00
parent ab0fdbc447
commit 06e3aa2eb3
2009 changed files with 193082 additions and 0 deletions

View File

@ -0,0 +1,49 @@
package excel
import (
"errors"
"fmt"
"github.com/tiger1103/gfast/v3/api/v1/common/tool"
"github.com/xuri/excelize/v2"
)
type Sheet struct {
Name string `json:"name"`
Rows [][]string `json:"rows"`
}
func ReadXlsx(xlsx string) (err error, sheet []Sheet) {
if !tool.PathExists(xlsx) {
return errors.New("文件不存在:" + xlsx), sheet
}
f, err := excelize.OpenFile(xlsx)
if err != nil {
fmt.Println(err)
return err, sheet
}
defer func() {
// 关闭工作簿
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
list := f.GetSheetList()
// 获取 Sheet1 上所有单元格
for _, sheetName := range list {
result, err := f.GetRows(sheetName)
if err != nil {
fmt.Println(err)
continue
}
sheet = append(sheet, Sheet{sheetName, result})
//rows = append(rows, result...)
}
//for _, row := range rows {
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
// fmt.Println()
//}
return nil, sheet
}

View File

@ -0,0 +1,25 @@
package proj
import (
_ "embed"
"github.com/dop251/goja"
)
//go:embed proj4.js
var proj4 string
var CGCS2000_to_WGS84 func(degrees int, cscs2000 [][]string) string
var WGS84_to_CGCS2000 func(degrees int, wgs84 [][]string) string
func InitProj() {
vm := goja.New()
vm.RunString(proj4)
vm.ExportTo(vm.Get("CGCS2000_to_WGS84"), &CGCS2000_to_WGS84)
vm.ExportTo(vm.Get("WGS84_to_CGCS2000"), &WGS84_to_CGCS2000)
//var ss [][]string
//ss = append(ss, []string{
// "106.545463204423", "23.467020901621", "805.6832",
//})
//s := WGS84_to_CGCS2000(108, ss)
//fmt.Println(s)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
go:
enabled: true

View File

@ -0,0 +1,19 @@
language: go
sudo: false
go:
- 1.8.x
- 1.9.x
- master
os:
- linux
before_install:
- go get -t -v ./...
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Jonas Palm
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,87 @@
go-shp
======
[![Build Status](https://travis-ci.org/jonas-p/go-shp.svg?branch=master)](https://travis-ci.org/jonas-p/go-shp)
[![Build status](https://ci.appveyor.com/api/projects/status/b64sntax4kxlouxa?svg=true)](https://ci.appveyor.com/project/fawick/go-shp)
[![Go Report Card](https://goreportcard.com/badge/github.com/jonas-p/go-shp)](https://goreportcard.com/report/github.com/jonas-p/go-shp)
[![Codevov](https://codecov.io/gh/jonas-p/go-shp/branch/master/graphs/badge.svg)](https://codecov.io/gh/jonas-p/go-shp)
Go library for reading and writing ESRI Shapefiles. This is a pure Golang implementation based on the ESRI Shapefile technical description.
### Usage
#### Installation
go get github.com/jonas-p/go-shp
#### Importing
```go
import "github.com/jonas-p/go-shp"
```
### Examples
#### Reading a shapefile
```go
// open a shapefile for reading
shape, err := shp.Open("points.shp")
if err != nil { log.Fatal(err) }
defer shape.Close()
// fields from the attribute table (DBF)
fields := shape.Fields()
// loop through all features in the shapefile
for shape.Next() {
n, p := shape.Shape()
// print feature
fmt.Println(reflect.TypeOf(p).Elem(), p.BBox())
// print attributes
for k, f := range fields {
val := shape.ReadAttribute(n, k)
fmt.Printf("\t%v: %v\n", f, val)
}
fmt.Println()
}
```
#### Creating a shapefile
```go
// points to write
points := []shp.Point{
shp.Point{10.0, 10.0},
shp.Point{10.0, 15.0},
shp.Point{15.0, 15.0},
shp.Point{15.0, 10.0},
}
// fields to write
fields := []shp.Field{
// String attribute field with length 25
shp.StringField("NAME", 25),
}
// create and open a shapefile for writing points
shape, err := shp.Create("points.shp", shp.POINT)
if err != nil { log.Fatal(err) }
defer shape.Close()
// setup fields for attributes
shape.SetFields(fields)
// write points and attributes
for n, point := range points {
shape.Write(&point)
// write attribute for object n for field 0 (NAME)
shape.WriteAttribute(n, 0, "Point " + strconv.Itoa(n + 1))
}
```
### Resources
- [Documentation on godoc.org](http://godoc.org/github.com/jonas-p/go-shp)
- [ESRI Shapefile Technical Description](http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf)

View File

@ -0,0 +1,26 @@
clone_folder: c:\go-shp
environment:
GOPATH: c:\gopath
branches:
only:
- master
init:
- ps: >-
$app = Get-WmiObject -Class Win32_Product -Filter "Vendor = 'http://golang.org'"
if ($app) {
$app.Uninstall()
}
install:
- rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.windows-amd64.msi
- msiexec /i go1.9.windows-amd64.msi /q
- go version
- go env
build_script:
- go test ./...

View File

@ -0,0 +1,27 @@
package shp
import (
"fmt"
"io"
)
// errReader is a helper to perform multiple successive read from another reader
// and do the error checking only once afterwards. It will not perform any new
// reads in case there was an error encountered earlier.
type errReader struct {
io.Reader
e error
n int64
}
func (er *errReader) Read(p []byte) (n int, err error) {
if er.e != nil {
return 0, fmt.Errorf("unable to read after previous error: %v", er.e)
}
n, err = er.Reader.Read(p)
if n < len(p) && err != nil {
er.e = err
}
er.n += int64(n)
return n, er.e
}

View 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[:]), " ")
}

View File

@ -0,0 +1,527 @@
package shp
import (
"bytes"
"io"
"io/ioutil"
"testing"
)
func pointsEqual(a, b []float64) bool {
if len(a) != len(b) {
return false
}
for k, v := range a {
if v != b[k] {
return false
}
}
return true
}
func getShapesFromFile(prefix string, t *testing.T) (shapes []Shape) {
filename := prefix + ".shp"
file, err := Open(filename)
if err != nil {
t.Fatal("Failed to open shapefile: " + filename + " (" + err.Error() + ")")
}
defer file.Close()
for file.Next() {
_, shape := file.Shape()
shapes = append(shapes, shape)
}
if file.Err() != nil {
t.Errorf("Error while getting shapes for %s: %v", prefix, file.Err())
}
return shapes
}
type shapeGetterFunc func(string, *testing.T) []Shape
type identityTestFunc func(*testing.T, [][]float64, []Shape)
func testPoint(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*Point)
if !ok {
t.Fatal("Failed to type assert.")
}
if !pointsEqual([]float64{p.X, p.Y}, points[n]) {
t.Error("Points did not match.")
}
}
}
func testPolyLine(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*PolyLine)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y}) {
t.Error("Points did not match.")
}
}
}
}
func testPolygon(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*Polygon)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y}) {
t.Error("Points did not match.")
}
}
}
}
func testMultiPoint(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*MultiPoint)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y}) {
t.Error("Points did not match.")
}
}
}
}
func testPointZ(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*PointZ)
if !ok {
t.Fatal("Failed to type assert.")
}
if !pointsEqual([]float64{p.X, p.Y, p.Z}, points[n]) {
t.Error("Points did not match.")
}
}
}
func testPolyLineZ(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*PolyLineZ)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y, p.ZArray[k]}) {
t.Error("Points did not match.")
}
}
}
}
func testPolygonZ(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*PolygonZ)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y, p.ZArray[k]}) {
t.Error("Points did not match.")
}
}
}
}
func testMultiPointZ(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*MultiPointZ)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y, p.ZArray[k]}) {
t.Error("Points did not match.")
}
}
}
}
func testPointM(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*PointM)
if !ok {
t.Fatal("Failed to type assert.")
}
if !pointsEqual([]float64{p.X, p.Y, p.M}, points[n]) {
t.Error("Points did not match.")
}
}
}
func testPolyLineM(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*PolyLineM)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y, p.MArray[k]}) {
t.Error("Points did not match.")
}
}
}
}
func testPolygonM(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*PolygonM)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y, p.MArray[k]}) {
t.Error("Points did not match.")
}
}
}
}
func testMultiPointM(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*MultiPointM)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y, p.MArray[k]}) {
t.Error("Points did not match.")
}
}
}
}
func testMultiPatch(t *testing.T, points [][]float64, shapes []Shape) {
for n, s := range shapes {
p, ok := s.(*MultiPatch)
if !ok {
t.Fatal("Failed to type assert.")
}
for k, point := range p.Points {
if !pointsEqual(points[n*3+k], []float64{point.X, point.Y, p.ZArray[k]}) {
t.Error("Points did not match.")
}
}
}
}
func testshapeIdentity(t *testing.T, prefix string, getter shapeGetterFunc) {
shapes := getter(prefix, t)
d := dataForReadTests[prefix]
if len(shapes) != d.count {
t.Errorf("Number of shapes for %s read was wrong. Wanted %d, got %d.", prefix, d.count, len(shapes))
}
d.tester(t, d.points, shapes)
}
func TestReadBBox(t *testing.T) {
tests := []struct {
filename string
want Box
}{
{"test_files/multipatch.shp", Box{0, 0, 10, 10}},
{"test_files/multipoint.shp", Box{0, 5, 10, 10}},
{"test_files/multipointm.shp", Box{0, 5, 10, 10}},
{"test_files/multipointz.shp", Box{0, 5, 10, 10}},
{"test_files/point.shp", Box{0, 5, 10, 10}},
{"test_files/pointm.shp", Box{0, 5, 10, 10}},
{"test_files/pointz.shp", Box{0, 5, 10, 10}},
{"test_files/polygon.shp", Box{0, 0, 5, 5}},
{"test_files/polygonm.shp", Box{0, 0, 5, 5}},
{"test_files/polygonz.shp", Box{0, 0, 5, 5}},
{"test_files/polyline.shp", Box{0, 0, 25, 25}},
{"test_files/polylinem.shp", Box{0, 0, 25, 25}},
{"test_files/polylinez.shp", Box{0, 0, 25, 25}},
}
for _, tt := range tests {
r, err := Open(tt.filename)
if err != nil {
t.Fatalf("%v", err)
}
if got := r.BBox().MinX; got != tt.want.MinX {
t.Errorf("got MinX = %v, want %v", got, tt.want.MinX)
}
if got := r.BBox().MinY; got != tt.want.MinY {
t.Errorf("got MinY = %v, want %v", got, tt.want.MinY)
}
if got := r.BBox().MaxX; got != tt.want.MaxX {
t.Errorf("got MaxX = %v, want %v", got, tt.want.MaxX)
}
if got := r.BBox().MaxY; got != tt.want.MaxY {
t.Errorf("got MaxY = %v, want %v", got, tt.want.MaxY)
}
}
}
type testCaseData struct {
points [][]float64
tester identityTestFunc
count int
}
var dataForReadTests = map[string]testCaseData{
"test_files/polygonm": {
points: [][]float64{
{0, 0, 0},
{0, 5, 5},
{5, 5, 10},
{5, 0, 15},
{0, 0, 0},
},
tester: testPolygonM,
count: 1,
},
"test_files/multipointm": {
points: [][]float64{
{10, 10, 100},
{5, 5, 50},
{0, 10, 75},
},
tester: testMultiPointM,
count: 1,
},
"test_files/multipatch": {
points: [][]float64{
{0, 0, 0},
{10, 0, 0},
{10, 10, 0},
{0, 10, 0},
{0, 0, 0},
{0, 10, 0},
{0, 10, 10},
{0, 0, 10},
{0, 0, 0},
{0, 10, 0},
{10, 0, 0},
{10, 0, 10},
{10, 10, 10},
{10, 10, 0},
{10, 0, 0},
{0, 0, 0},
{0, 0, 10},
{10, 0, 10},
{10, 0, 0},
{0, 0, 0},
{10, 10, 0},
{10, 10, 10},
{0, 10, 10},
{0, 10, 0},
{10, 10, 0},
{0, 0, 10},
{0, 10, 10},
{10, 10, 10},
{10, 0, 10},
{0, 0, 10},
},
tester: testMultiPatch,
count: 1,
},
"test_files/point": {
points: [][]float64{
{10, 10},
{5, 5},
{0, 10},
},
tester: testPoint,
count: 3,
},
"test_files/polyline": {
points: [][]float64{
{0, 0},
{5, 5},
{10, 10},
{15, 15},
{20, 20},
{25, 25},
},
tester: testPolyLine,
count: 2,
},
"test_files/polygon": {
points: [][]float64{
{0, 0},
{0, 5},
{5, 5},
{5, 0},
{0, 0},
},
tester: testPolygon,
count: 1,
},
"test_files/multipoint": {
points: [][]float64{
{10, 10},
{5, 5},
{0, 10},
},
tester: testMultiPoint,
count: 1,
},
"test_files/pointz": {
points: [][]float64{
{10, 10, 100},
{5, 5, 50},
{0, 10, 75},
},
tester: testPointZ,
count: 3,
},
"test_files/polylinez": {
points: [][]float64{
{0, 0, 0},
{5, 5, 5},
{10, 10, 10},
{15, 15, 15},
{20, 20, 20},
{25, 25, 25},
},
tester: testPolyLineZ,
count: 2,
},
"test_files/polygonz": {
points: [][]float64{
{0, 0, 0},
{0, 5, 5},
{5, 5, 10},
{5, 0, 15},
{0, 0, 0},
},
tester: testPolygonZ,
count: 1,
},
"test_files/multipointz": {
points: [][]float64{
{10, 10, 100},
{5, 5, 50},
{0, 10, 75},
},
tester: testMultiPointZ,
count: 1,
},
"test_files/pointm": {
points: [][]float64{
{10, 10, 100},
{5, 5, 50},
{0, 10, 75},
},
tester: testPointM,
count: 3,
},
"test_files/polylinem": {
points: [][]float64{
{0, 0, 0},
{5, 5, 5},
{10, 10, 10},
{15, 15, 15},
{20, 20, 20},
{25, 25, 25},
},
tester: testPolyLineM,
count: 2,
},
}
func TestReadPoint(t *testing.T) {
testshapeIdentity(t, "test_files/point", getShapesFromFile)
}
func TestReadPolyLine(t *testing.T) {
testshapeIdentity(t, "test_files/polyline", getShapesFromFile)
}
func TestReadPolygon(t *testing.T) {
testshapeIdentity(t, "test_files/polygon", getShapesFromFile)
}
func TestReadMultiPoint(t *testing.T) {
testshapeIdentity(t, "test_files/multipoint", getShapesFromFile)
}
func TestReadPointZ(t *testing.T) {
testshapeIdentity(t, "test_files/pointz", getShapesFromFile)
}
func TestReadPolyLineZ(t *testing.T) {
testshapeIdentity(t, "test_files/polylinez", getShapesFromFile)
}
func TestReadPolygonZ(t *testing.T) {
testshapeIdentity(t, "test_files/polygonz", getShapesFromFile)
}
func TestReadMultiPointZ(t *testing.T) {
testshapeIdentity(t, "test_files/multipointz", getShapesFromFile)
}
func TestReadPointM(t *testing.T) {
testshapeIdentity(t, "test_files/pointm", getShapesFromFile)
}
func TestReadPolyLineM(t *testing.T) {
testshapeIdentity(t, "test_files/polylinem", getShapesFromFile)
}
func TestReadPolygonM(t *testing.T) {
testshapeIdentity(t, "test_files/polygonm", getShapesFromFile)
}
func TestReadMultiPointM(t *testing.T) {
testshapeIdentity(t, "test_files/multipointm", getShapesFromFile)
}
func TestReadMultiPatch(t *testing.T) {
testshapeIdentity(t, "test_files/multipatch", getShapesFromFile)
}
func newReadSeekCloser(b []byte) readSeekCloser {
return struct {
io.Closer
io.ReadSeeker
}{
ioutil.NopCloser(nil),
bytes.NewReader(b),
}
}
func TestReadInvalidShapeType(t *testing.T) {
record := []byte{
0, 0, 0, 0,
0, 0, 0, 0,
255, 255, 255, 255, // shape type
}
tests := []struct {
r interface {
Next() bool
Err() error
}
name string
}{
{&Reader{shp: newReadSeekCloser(record), filelength: int64(len(record))}, "reader"},
{&seqReader{shp: newReadSeekCloser(record), filelength: int64(len(record))}, "seqReader"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.r.Next() {
t.Fatal("read unsupported shape type without stopping")
}
if test.r.Err() == nil {
t.Fatal("read unsupported shape type without error")
}
})
}
}

View File

@ -0,0 +1,235 @@
package shp
import (
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"math"
"strings"
)
// SequentialReader is the interface that allows reading shapes and attributes one after another. It also embeds io.Closer.
type SequentialReader interface {
// Close() frees the resources allocated by the SequentialReader.
io.Closer
// Next() tries to advance the reading by one shape and one attribute row
// and returns true if the read operation could be performed without any
// error.
Next() bool
// Shape returns the index and the last read shape. If the SequentialReader
// encountered any errors, nil is returned for the Shape.
Shape() (int, Shape)
// Attribute returns the value of the n-th attribute in the current row. If
// the SequentialReader encountered any errors, the empty string is
// returned.
Attribute(n int) string
// Fields returns the fields of the database. If the SequentialReader
// encountered any errors, nil is returned.
Fields() []Field
// Err returns the last non-EOF error encountered.
Err() error
}
// Attributes returns all attributes of the shape that sr was last advanced to.
func Attributes(sr SequentialReader) []string {
if sr.Err() != nil {
return nil
}
s := make([]string, len(sr.Fields()))
for i := range s {
s[i] = sr.Attribute(i)
}
return s
}
// AttributeCount returns the number of fields of the database.
func AttributeCount(sr SequentialReader) int {
return len(sr.Fields())
}
// seqReader implements SequentialReader based on external io.ReadCloser
// instances
type seqReader struct {
shp, dbf io.ReadCloser
err error
geometryType ShapeType
bbox Box
shape Shape
num int32
filelength int64
dbfFields []Field
dbfNumRecords int32
dbfHeaderLength int16
dbfRecordLength int16
dbfRow []byte
}
// Read and parse headers in the Shapefile. This will fill out GeometryType,
// filelength and bbox.
func (sr *seqReader) readHeaders() {
// contrary to Reader.readHeaders we cannot seek with the ReadCloser, so we
// need to trust the filelength in the header
er := &errReader{Reader: sr.shp}
// shp headers
io.CopyN(ioutil.Discard, er, 24)
var l int32
binary.Read(er, binary.BigEndian, &l)
sr.filelength = int64(l) * 2
io.CopyN(ioutil.Discard, er, 4)
binary.Read(er, binary.LittleEndian, &sr.geometryType)
sr.bbox.MinX = readFloat64(er)
sr.bbox.MinY = readFloat64(er)
sr.bbox.MaxX = readFloat64(er)
sr.bbox.MaxY = readFloat64(er)
io.CopyN(ioutil.Discard, er, 32) // skip four float64: Zmin, Zmax, Mmin, Max
if er.e != nil {
sr.err = fmt.Errorf("Error when reading SHP header: %v", er.e)
return
}
// dbf header
er = &errReader{Reader: sr.dbf}
if sr.dbf == nil {
return
}
io.CopyN(ioutil.Discard, er, 4)
binary.Read(er, binary.LittleEndian, &sr.dbfNumRecords)
binary.Read(er, binary.LittleEndian, &sr.dbfHeaderLength)
binary.Read(er, binary.LittleEndian, &sr.dbfRecordLength)
io.CopyN(ioutil.Discard, er, 20) // skip padding
numFields := int(math.Floor(float64(sr.dbfHeaderLength-33) / 32.0))
sr.dbfFields = make([]Field, numFields)
binary.Read(er, binary.LittleEndian, &sr.dbfFields)
buf := make([]byte, 1)
er.Read(buf[:])
if er.e != nil {
sr.err = fmt.Errorf("Error when reading DBF header: %v", er.e)
return
}
if buf[0] != 0x0d {
sr.err = fmt.Errorf("Field descriptor array terminator not found")
return
}
sr.dbfRow = make([]byte, sr.dbfRecordLength)
}
// Next implements a method of interface SequentialReader for seqReader.
func (sr *seqReader) Next() bool {
if sr.err != nil {
return false
}
var num, size int32
var shapetype ShapeType
// read shape
er := &errReader{Reader: sr.shp}
binary.Read(er, binary.BigEndian, &num)
binary.Read(er, binary.BigEndian, &size)
binary.Read(er, binary.LittleEndian, &shapetype)
if er.e != nil {
if er.e != io.EOF {
sr.err = fmt.Errorf("Error when reading shapefile header: %v", er.e)
} else {
sr.err = io.EOF
}
return false
}
sr.num = num
var err error
sr.shape, err = newShape(shapetype)
if err != nil {
sr.err = fmt.Errorf("Error decoding shape type: %v", err)
return false
}
sr.shape.read(er)
switch {
case er.e == io.EOF:
// io.EOF means end-of-file was reached gracefully after all
// shape-internal reads succeeded, so it's not a reason stop
// iterating over all shapes.
er.e = nil
case er.e != nil:
sr.err = fmt.Errorf("Error while reading next shape: %v", er.e)
return false
}
skipBytes := int64(size)*2 + 8 - er.n
_, ce := io.CopyN(ioutil.Discard, er, skipBytes)
if er.e != nil {
sr.err = er.e
return false
}
if ce != nil {
sr.err = fmt.Errorf("Error when discarding bytes on sequential read: %v", ce)
return false
}
if _, err := io.ReadFull(sr.dbf, sr.dbfRow); err != nil {
sr.err = fmt.Errorf("Error when reading DBF row: %v", err)
return false
}
if sr.dbfRow[0] != 0x20 && sr.dbfRow[0] != 0x2a {
sr.err = fmt.Errorf("Attribute row %d starts with incorrect deletion indicator", num)
}
return sr.err == nil
}
// Shape implements a method of interface SequentialReader for seqReader.
func (sr *seqReader) Shape() (int, Shape) {
return int(sr.num) - 1, sr.shape
}
// Attribute implements a method of interface SequentialReader for seqReader.
func (sr *seqReader) Attribute(n int) string {
if sr.err != nil {
return ""
}
start := 1
f := 0
for ; f < n; f++ {
start += int(sr.dbfFields[f].Size)
}
s := string(sr.dbfRow[start : start+int(sr.dbfFields[f].Size)])
return strings.Trim(s, " ")
}
// Err returns the first non-EOF error that was encountered.
func (sr *seqReader) Err() error {
if sr.err == io.EOF {
return nil
}
return sr.err
}
// Close closes the seqReader and free all the allocated resources.
func (sr *seqReader) Close() error {
if err := sr.shp.Close(); err != nil {
return err
}
if err := sr.dbf.Close(); err != nil {
return err
}
return nil
}
// Fields returns a slice of the fields that are present in the DBF table.
func (sr *seqReader) Fields() []Field {
return sr.dbfFields
}
// SequentialReaderFromExt returns a new SequentialReader that interprets shp
// as a source of shapes whose attributes can be retrieved from dbf.
func SequentialReaderFromExt(shp, dbf io.ReadCloser) SequentialReader {
sr := &seqReader{shp: shp, dbf: dbf}
sr.readHeaders()
return sr
}

View File

@ -0,0 +1,43 @@
package shp
import (
"os"
"testing"
)
func openFile(name string, t *testing.T) *os.File {
f, err := os.Open(name)
if err != nil {
t.Fatalf("Failed to open %s: %v", name, err)
}
return f
}
func getShapesSequentially(prefix string, t *testing.T) (shapes []Shape) {
shp := openFile(prefix+".shp", t)
dbf := openFile(prefix+".dbf", t)
sr := SequentialReaderFromExt(shp, dbf)
if err := sr.Err(); err != nil {
t.Fatalf("Error when iterating over the shapefile header: %v", err)
}
for sr.Next() {
_, shape := sr.Shape()
shapes = append(shapes, shape)
}
if err := sr.Err(); err != nil {
t.Errorf("Error when iterating over the shapes: %v", err)
}
if err := sr.Close(); err != nil {
t.Errorf("Could not close sequential reader: %v", err)
}
return shapes
}
func TestSequentialReader(t *testing.T) {
for prefix := range dataForReadTests {
t.Logf("Testing sequential read for %s", prefix)
testshapeIdentity(t, prefix, getShapesSequentially)
}
}

View File

@ -0,0 +1,612 @@
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
}

View File

@ -0,0 +1,22 @@
package shp
import "testing"
func TestBoxExtend(t *testing.T) {
a := Box{-124.763068, 45.543541, -116.915989, 49.002494}
b := Box{-92.888114, 42.49192, -86.805415, 47.080621}
a.Extend(b)
c := Box{-124.763068, 42.49192, -86.805415, 49.002494}
if a.MinX != c.MinX {
t.Errorf("a.MinX = %v, want %v", a.MinX, c.MinX)
}
if a.MinY != c.MinY {
t.Errorf("a.MinY = %v, want %v", a.MinY, c.MinY)
}
if a.MaxX != c.MaxX {
t.Errorf("a.MaxX = %v, want %v", a.MaxX, c.MaxX)
}
if a.MaxY != c.MaxY {
t.Errorf("a.MaxY = %v, want %v", a.MaxY, c.MaxY)
}
}

View File

@ -0,0 +1,31 @@
// Code generated by "stringer -type=ShapeType"; DO NOT EDIT.
package shp
import "strconv"
const _ShapeType_name = "NULLPOINTPOLYLINEPOLYGONMULTIPOINTPOINTZPOLYLINEZPOLYGONZMULTIPOINTZPOINTMPOLYLINEMPOLYGONMMULTIPOINTMMULTIPATCH"
var _ShapeType_map = map[ShapeType]string{
0: _ShapeType_name[0:4],
1: _ShapeType_name[4:9],
3: _ShapeType_name[9:17],
5: _ShapeType_name[17:24],
8: _ShapeType_name[24:34],
11: _ShapeType_name[34:40],
13: _ShapeType_name[40:49],
15: _ShapeType_name[49:57],
18: _ShapeType_name[57:68],
21: _ShapeType_name[68:74],
23: _ShapeType_name[74:83],
25: _ShapeType_name[83:91],
28: _ShapeType_name[91:102],
31: _ShapeType_name[102:112],
}
func (i ShapeType) String() string {
if str, ok := _ShapeType_map[i]; ok {
return str
}
return "ShapeType(" + strconv.FormatInt(int64(i), 10) + ")"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,345 @@
package shp
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"path/filepath"
"strconv"
"strings"
)
// Writer is the type that is used to write a new shapefile.
type Writer struct {
filename string
shp writeSeekCloser
shx writeSeekCloser
GeometryType ShapeType
num int32
bbox Box
dbf writeSeekCloser
dbfFields []Field
dbfHeaderLength int16
dbfRecordLength int16
}
type writeSeekCloser interface {
io.Writer
io.Seeker
io.Closer
}
// Create returns a point to new Writer and the first error that was
// encountered. In case an error occurred the returned Writer point will be nil
// This also creates a corresponding SHX file. It is important to use Close()
// when done because that method writes all the headers for each file (SHP, SHX
// and DBF).
// If filename does not end on ".shp" already, it will be treated as the basename
// for the file and the ".shp" extension will be appended to that name.
func Create(filename string, t ShapeType) (*Writer, error) {
if strings.HasSuffix(strings.ToLower(filename), ".shp") {
filename = filename[0 : len(filename)-4]
}
shp, err := os.Create(filename + ".shp")
if err != nil {
return nil, err
}
shx, err := os.Create(filename + ".shx")
if err != nil {
return nil, err
}
shp.Seek(100, io.SeekStart)
shx.Seek(100, io.SeekStart)
w := &Writer{
filename: filename,
shp: shp,
shx: shx,
GeometryType: t,
}
return w, nil
}
// Append returns a Writer pointer that will append to the given shapefile and
// the first error that was encounted during creation of that Writer. The
// shapefile must have a valid index file.
func Append(filename string) (*Writer, error) {
shp, err := os.OpenFile(filename, os.O_RDWR, 0666)
if err != nil {
return nil, err
}
ext := filepath.Ext(filename)
basename := filename[:len(filename)-len(ext)]
w := &Writer{
filename: basename,
shp: shp,
}
_, err = shp.Seek(32, io.SeekStart)
if err != nil {
return nil, fmt.Errorf("cannot seek to SHP geometry type: %v", err)
}
err = binary.Read(shp, binary.LittleEndian, &w.GeometryType)
if err != nil {
return nil, fmt.Errorf("cannot read geometry type: %v", err)
}
er := &errReader{Reader: shp}
w.bbox.MinX = readFloat64(er)
w.bbox.MinY = readFloat64(er)
w.bbox.MaxX = readFloat64(er)
w.bbox.MaxY = readFloat64(er)
if er.e != nil {
return nil, fmt.Errorf("cannot read bounding box: %v", er.e)
}
shx, err := os.OpenFile(basename+".shx", os.O_RDWR, 0666)
if os.IsNotExist(err) {
// TODO allow index file to not exist, in that case just
// read through all the shapes and create it on the fly
}
if err != nil {
return nil, fmt.Errorf("cannot open shapefile index: %v", err)
}
_, err = shx.Seek(-8, io.SeekEnd)
if err != nil {
return nil, fmt.Errorf("cannot seek to last shape index: %v", err)
}
var offset int32
err = binary.Read(shx, binary.BigEndian, &offset)
if err != nil {
return nil, fmt.Errorf("cannot read last shape index: %v", err)
}
offset = offset * 2
_, err = shp.Seek(int64(offset), io.SeekStart)
if err != nil {
return nil, fmt.Errorf("cannot seek to last shape: %v", err)
}
err = binary.Read(shp, binary.BigEndian, &w.num)
if err != nil {
return nil, fmt.Errorf("cannot read number of last shape: %v", err)
}
_, err = shp.Seek(0, io.SeekEnd)
if err != nil {
return nil, fmt.Errorf("cannot seek to SHP end: %v", err)
}
_, err = shx.Seek(0, io.SeekEnd)
if err != nil {
return nil, fmt.Errorf("cannot seek to SHX end: %v", err)
}
w.shx = shx
dbf, err := os.Open(basename + ".dbf")
if os.IsNotExist(err) {
return w, nil // it's okay if the DBF does not exist
}
if err != nil {
return nil, fmt.Errorf("cannot open DBF: %v", err)
}
_, err = dbf.Seek(8, io.SeekStart)
if err != nil {
return nil, fmt.Errorf("cannot seek in DBF: %v", err)
}
err = binary.Read(dbf, binary.LittleEndian, &w.dbfHeaderLength)
if err != nil {
return nil, fmt.Errorf("cannot read header length from DBF: %v", err)
}
err = binary.Read(dbf, binary.LittleEndian, &w.dbfRecordLength)
if err != nil {
return nil, fmt.Errorf("cannot read record length from DBF: %v", err)
}
_, err = dbf.Seek(20, io.SeekCurrent) // skip padding
if err != nil {
return nil, fmt.Errorf("cannot seek in DBF: %v", err)
}
numFields := int(math.Floor(float64(w.dbfHeaderLength-33) / 32.0))
w.dbfFields = make([]Field, numFields)
err = binary.Read(dbf, binary.LittleEndian, &w.dbfFields)
if err != nil {
return nil, fmt.Errorf("cannot read number of fields from DBF: %v", err)
}
_, err = dbf.Seek(0, io.SeekEnd) // skip padding
if err != nil {
return nil, fmt.Errorf("cannot seek to DBF end: %v", err)
}
w.dbf = dbf
return w, nil
}
// Write shape to the Shapefile. This also creates
// a record in the SHX file and DBF file (if it is
// initialized). Returns the index of the written object
// which can be used in WriteAttribute.
func (w *Writer) Write(shape Shape) int32 {
// increate bbox
if w.num == 0 {
w.bbox = shape.BBox()
} else {
w.bbox.Extend(shape.BBox())
}
w.num++
binary.Write(w.shp, binary.BigEndian, w.num)
w.shp.Seek(4, io.SeekCurrent)
start, _ := w.shp.Seek(0, io.SeekCurrent)
binary.Write(w.shp, binary.LittleEndian, w.GeometryType)
shape.write(w.shp)
finish, _ := w.shp.Seek(0, io.SeekCurrent)
length := int32(math.Floor((float64(finish) - float64(start)) / 2.0))
w.shp.Seek(start-4, io.SeekStart)
binary.Write(w.shp, binary.BigEndian, length)
w.shp.Seek(finish, io.SeekStart)
// write shx
binary.Write(w.shx, binary.BigEndian, int32((start-8)/2))
binary.Write(w.shx, binary.BigEndian, length)
// write empty record to dbf
if w.dbf != nil {
w.writeEmptyRecord()
}
return w.num - 1
}
// Close closes the Writer. This must be used at the end of
// the transaction because it writes the correct headers
// to the SHP/SHX and DBF files before closing.
func (w *Writer) Close() {
w.writeHeader(w.shx)
w.writeHeader(w.shp)
w.shp.Close()
w.shx.Close()
if w.dbf == nil {
w.SetFields([]Field{})
}
w.writeDbfHeader(w.dbf)
w.dbf.Close()
}
// writeHeader wrires SHP/SHX headers to ws.
func (w *Writer) writeHeader(ws io.WriteSeeker) {
filelength, _ := ws.Seek(0, io.SeekEnd)
if filelength == 0 {
filelength = 100
}
ws.Seek(0, io.SeekStart)
// file code
binary.Write(ws, binary.BigEndian, []int32{9994, 0, 0, 0, 0, 0})
// file length
binary.Write(ws, binary.BigEndian, int32(filelength/2))
// version and shape type
binary.Write(ws, binary.LittleEndian, []int32{1000, int32(w.GeometryType)})
// bounding box
binary.Write(ws, binary.LittleEndian, w.bbox)
// elevation, measure
binary.Write(ws, binary.LittleEndian, []float64{0.0, 0.0, 0.0, 0.0})
}
// writeDbfHeader writes a DBF header to ws.
func (w *Writer) writeDbfHeader(ws io.WriteSeeker) {
ws.Seek(0, 0)
// version, year (YEAR-1990), month, day
binary.Write(ws, binary.LittleEndian, []byte{3, 24, 5, 3})
// number of records
binary.Write(ws, binary.LittleEndian, w.num)
// header length, record length
binary.Write(ws, binary.LittleEndian, []int16{w.dbfHeaderLength, w.dbfRecordLength})
// padding
binary.Write(ws, binary.LittleEndian, make([]byte, 20))
for _, field := range w.dbfFields {
binary.Write(ws, binary.LittleEndian, field)
}
// end with return
ws.Write([]byte("\r"))
}
// SetFields sets field values in the DBF. This initializes the DBF file and
// should be used prior to writing any attributes.
func (w *Writer) SetFields(fields []Field) error {
if w.dbf != nil {
return errors.New("Cannot set fields in existing dbf")
}
var err error
w.dbf, err = os.Create(w.filename + ".dbf")
if err != nil {
return fmt.Errorf("Failed to open %s.dbf: %v", w.filename, err)
}
w.dbfFields = fields
// calculate record length
w.dbfRecordLength = int16(1)
for _, field := range w.dbfFields {
w.dbfRecordLength += int16(field.Size)
}
// header lengh
w.dbfHeaderLength = int16(len(w.dbfFields)*32 + 33)
// fill header space with empty bytes for now
buf := make([]byte, w.dbfHeaderLength)
binary.Write(w.dbf, binary.LittleEndian, buf)
// write empty records
for n := int32(0); n < w.num; n++ {
w.writeEmptyRecord()
}
return nil
}
// Writes an empty record to the end of the DBF. This
// works by seeking to the end of the file and writing
// dbfRecordLength number of bytes. The first byte is a
// space that indicates a new record.
func (w *Writer) writeEmptyRecord() {
w.dbf.Seek(0, io.SeekEnd)
buf := make([]byte, w.dbfRecordLength)
buf[0] = ' '
binary.Write(w.dbf, binary.LittleEndian, buf)
}
// WriteAttribute writes value for field into the given row in the DBF. Row
// number should be the same as the order the Shape was written to the
// Shapefile. The field value corresponds to the field in the slice used in
// SetFields.
func (w *Writer) WriteAttribute(row int, field int, value interface{}) error {
var buf []byte
switch v := value.(type) {
case int:
buf = []byte(strconv.Itoa(v))
case float64:
precision := w.dbfFields[field].Precision
buf = []byte(strconv.FormatFloat(v, 'f', int(precision), 64))
case string:
buf = []byte(v)
default:
return fmt.Errorf("Unsupported value type: %T", v)
}
if w.dbf == nil {
return errors.New("Initialize DBF by using SetFields first")
}
if sz := int(w.dbfFields[field].Size); len(buf) > sz {
return fmt.Errorf("Unable to write field %v: %q exceeds field length %v", field, buf, sz)
}
seekTo := 1 + int64(w.dbfHeaderLength) + (int64(row) * int64(w.dbfRecordLength))
for n := 0; n < field; n++ {
seekTo += int64(w.dbfFields[n].Size)
}
w.dbf.Seek(seekTo, io.SeekStart)
return binary.Write(w.dbf, binary.LittleEndian, buf)
}
// BBox returns the bounding box of the Writer.
func (w *Writer) BBox() Box {
return w.bbox
}

View File

@ -0,0 +1,209 @@
package shp
import (
"bytes"
"io"
"os"
"reflect"
"testing"
)
var filenamePrefix = "test_files/write_"
func removeShapefile(filename string) {
os.Remove(filename + ".shp")
os.Remove(filename + ".shx")
os.Remove(filename + ".dbf")
}
func pointsToFloats(points []Point) [][]float64 {
floats := make([][]float64, len(points))
for k, v := range points {
floats[k] = make([]float64, 2)
floats[k][0] = v.X
floats[k][1] = v.Y
}
return floats
}
func TestAppend(t *testing.T) {
filename := filenamePrefix + "point"
defer removeShapefile(filename)
points := [][]float64{
{0.0, 0.0},
{5.0, 5.0},
{10.0, 10.0},
}
shape, err := Create(filename+".shp", POINT)
if err != nil {
t.Fatal(err)
}
for _, p := range points {
shape.Write(&Point{p[0], p[1]})
}
wantNum := shape.num
shape.Close()
newPoints := [][]float64{
{15.0, 15.0},
{20.0, 20.0},
{25.0, 25.0},
}
shape, err = Append(filename + ".shp")
if err != nil {
t.Fatal(err)
}
if shape.GeometryType != POINT {
t.Fatalf("wanted geo type %d, got %d", POINT, shape.GeometryType)
}
if shape.num != wantNum {
t.Fatalf("wrong 'num', wanted type %d, got %d", wantNum, shape.num)
}
for _, p := range newPoints {
shape.Write(&Point{p[0], p[1]})
}
points = append(points, newPoints...)
shapes := getShapesFromFile(filename, t)
if len(shapes) != len(points) {
t.Error("Number of shapes read was wrong")
}
testPoint(t, points, shapes)
}
func TestWritePoint(t *testing.T) {
filename := filenamePrefix + "point"
defer removeShapefile(filename)
points := [][]float64{
{0.0, 0.0},
{5.0, 5.0},
{10.0, 10.0},
}
shape, err := Create(filename+".shp", POINT)
if err != nil {
t.Fatal(err)
}
for _, p := range points {
shape.Write(&Point{p[0], p[1]})
}
shape.Close()
shapes := getShapesFromFile(filename, t)
if len(shapes) != len(points) {
t.Error("Number of shapes read was wrong")
}
testPoint(t, points, shapes)
}
func TestWritePolyLine(t *testing.T) {
filename := filenamePrefix + "polyline"
defer removeShapefile(filename)
points := [][]Point{
{Point{0.0, 0.0}, Point{5.0, 5.0}},
{Point{10.0, 10.0}, Point{15.0, 15.0}},
}
shape, err := Create(filename+".shp", POLYLINE)
if err != nil {
t.Log(shape, err)
}
l := NewPolyLine(points)
lWant := &PolyLine{
Box: Box{MinX: 0, MinY: 0, MaxX: 15, MaxY: 15},
NumParts: 2,
NumPoints: 4,
Parts: []int32{0, 2},
Points: []Point{{X: 0, Y: 0},
{X: 5, Y: 5},
{X: 10, Y: 10},
{X: 15, Y: 15},
},
}
if !reflect.DeepEqual(l, lWant) {
t.Errorf("incorrect NewLine: have: %+v; want: %+v", l, lWant)
}
shape.Write(l)
shape.Close()
shapes := getShapesFromFile(filename, t)
if len(shapes) != 1 {
t.Error("Number of shapes read was wrong")
}
testPolyLine(t, pointsToFloats(flatten(points)), shapes)
}
type seekTracker struct {
io.Writer
offset int64
}
func (s *seekTracker) Seek(offset int64, whence int) (int64, error) {
s.offset = offset
return s.offset, nil
}
func (s *seekTracker) Close() error {
return nil
}
func TestWriteAttribute(t *testing.T) {
buf := new(bytes.Buffer)
s := &seekTracker{Writer: buf}
w := Writer{
dbf: s,
dbfFields: []Field{
StringField("A_STRING", 6),
FloatField("A_FLOAT", 8, 4),
NumberField("AN_INT", 4),
},
dbfRecordLength: 100,
}
tests := []struct {
name string
row int
field int
data interface{}
wantOffset int64
wantData string
}{
{"string-0", 0, 0, "test", 1, "test"},
{"string-0-overflow-1", 0, 0, "overflo", 0, ""},
{"string-0-overflow-n", 0, 0, "overflowing", 0, ""},
{"string-3", 3, 0, "things", 301, "things"},
{"float-0", 0, 1, 123.44, 7, "123.4400"},
{"float-0-overflow-1", 0, 1, 1234.0, 0, ""},
{"float-0-overflow-n", 0, 1, 123456789.0, 0, ""},
{"int-0", 0, 2, 4242, 15, "4242"},
{"int-0-overflow-1", 0, 2, 42424, 0, ""},
{"int-0-overflow-n", 0, 2, 42424343, 0, ""},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
buf.Reset()
s.offset = 0
err := w.WriteAttribute(test.row, test.field, test.data)
if buf.String() != test.wantData {
t.Errorf("got data: %v, want: %v", buf.String(), test.wantData)
}
if s.offset != test.wantOffset {
t.Errorf("got seek offset: %v, want: %v", s.offset, test.wantOffset)
}
if err == nil && test.wantData == "" {
t.Error("got no data and no error")
}
})
}
}

View File

@ -0,0 +1,151 @@
package shp
import (
"archive/zip"
"fmt"
"io"
"path"
"strings"
)
// ZipReader provides an interface for reading Shapefiles that are compressed in a ZIP archive.
type ZipReader struct {
sr SequentialReader
z *zip.ReadCloser
}
// openFromZIP is convenience function for opening the file called name that is
// compressed in z for reading.
func openFromZIP(z *zip.ReadCloser, name string) (io.ReadCloser, error) {
for _, f := range z.File {
if f.Name == name {
return f.Open()
}
}
return nil, fmt.Errorf("No such file in archive: %s", name)
}
// OpenZip opens a ZIP file that contains a single shapefile.
func OpenZip(zipFilePath string) (*ZipReader, error) {
z, err := zip.OpenReader(zipFilePath)
if err != nil {
return nil, err
}
zr := &ZipReader{
z: z,
}
shapeFiles := shapesInZip(z)
if len(shapeFiles) == 0 {
return nil, fmt.Errorf("archive does not contain a .shp file")
}
if len(shapeFiles) > 1 {
return nil, fmt.Errorf("archive does contain multiple .shp files")
}
shp, err := openFromZIP(zr.z, shapeFiles[0].Name)
if err != nil {
return nil, err
}
withoutExt := strings.TrimSuffix(shapeFiles[0].Name, ".shp")
// dbf is optional, so no error checking here
dbf, _ := openFromZIP(zr.z, withoutExt+".dbf")
zr.sr = SequentialReaderFromExt(shp, dbf)
return zr, nil
}
// ShapesInZip returns a string-slice with the names (i.e. relatives paths in
// archive file tree) of all shapes that are in the ZIP archive at zipFilePath.
func ShapesInZip(zipFilePath string) ([]string, error) {
var names []string
z, err := zip.OpenReader(zipFilePath)
if err != nil {
return nil, err
}
shapeFiles := shapesInZip(z)
for i := range shapeFiles {
names = append(names, shapeFiles[i].Name)
}
return names, nil
}
func shapesInZip(z *zip.ReadCloser) []*zip.File {
var shapeFiles []*zip.File
for _, f := range z.File {
if strings.HasSuffix(f.Name, ".shp") {
shapeFiles = append(shapeFiles, f)
}
}
return shapeFiles
}
// OpenShapeFromZip opens a shape file that is contained in a ZIP archive. The
// parameter name is name of the shape file.
// The name of the shapefile must be a relative path: it must not start with a
// drive letter (e.g. C:) or leading slash, and only forward slashes are
// allowed. These rules are the same as in
// https://golang.org/pkg/archive/zip/#FileHeader.
func OpenShapeFromZip(zipFilePath string, name string) (*ZipReader, error) {
z, err := zip.OpenReader(zipFilePath)
if err != nil {
return nil, err
}
zr := &ZipReader{
z: z,
}
shp, err := openFromZIP(zr.z, name)
if err != nil {
return nil, err
}
// dbf is optional, so no error checking here
prefix := strings.TrimSuffix(name, path.Ext(name))
dbf, _ := openFromZIP(zr.z, prefix+".dbf")
zr.sr = SequentialReaderFromExt(shp, dbf)
return zr, nil
}
// Close closes the ZipReader and frees the allocated resources.
func (zr *ZipReader) Close() error {
s := ""
err := zr.sr.Close()
if err != nil {
s += err.Error() + ". "
}
err = zr.z.Close()
if err != nil {
s += err.Error() + ". "
}
if s != "" {
return fmt.Errorf(s)
}
return nil
}
// Next reads the next shape in the shapefile and the next row in the DBF. Call
// Shape() and Attribute() to access the values.
func (zr *ZipReader) Next() bool {
return zr.sr.Next()
}
// Shape returns the shape that was last read as well as the current index.
func (zr *ZipReader) Shape() (int, Shape) {
return zr.sr.Shape()
}
// Attribute returns the n-th field of the last row that was read. If there
// were any errors before, the empty string is returned.
func (zr *ZipReader) Attribute(n int) string {
return zr.sr.Attribute(n)
}
// Fields returns a slice of Fields that are present in the
// DBF table.
func (zr *ZipReader) Fields() []Field {
return zr.sr.Fields()
}
// Err returns the last non-EOF error that was encountered by this ZipReader.
func (zr *ZipReader) Err() error {
return zr.sr.Err()
}

View File

@ -0,0 +1,236 @@
package shp
import (
"archive/zip"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"testing"
)
func compressFileToZIP(zw *zip.Writer, src, tgt string, t *testing.T) {
r, err := os.Open(src)
if err != nil {
t.Fatalf("Could not open for compression %s: %v", src, err)
}
w, err := zw.Create(tgt)
if err != nil {
t.Fatalf("Could not start to compress %s: %v", tgt, err)
}
_, err = io.Copy(w, r)
if err != nil {
t.Fatalf("Could not compress contents for %s: %v", tgt, err)
}
}
// createTempZIP packs the SHP, SHX, and DBF into a ZIP in a temporary
// directory
func createTempZIP(prefix string, t *testing.T) (dir, filename string) {
dir, err := ioutil.TempDir("", "go-shp-test")
if err != nil {
t.Fatalf("Could not create temporary directory: %v", err)
}
base := filepath.Base(prefix)
zipName := base + ".zip"
w, err := os.Create(filepath.Join(dir, zipName))
if err != nil {
t.Fatalf("Could not create temporary zip file: %v", err)
}
zw := zip.NewWriter(w)
for _, suffix := range []string{".shp", ".shx", ".dbf"} {
compressFileToZIP(zw, prefix+suffix, base+suffix, t)
}
if err := zw.Close(); err != nil {
t.Fatalf("Could not close the written zip: %v", err)
}
return dir, zipName
}
func getShapesZipped(prefix string, t *testing.T) (shapes []Shape) {
dir, filename := createTempZIP(prefix, t)
defer os.RemoveAll(dir)
zr, err := OpenZip(filepath.Join(dir, filename))
if err != nil {
t.Errorf("Error when opening zip file: %v", err)
}
for zr.Next() {
_, shape := zr.Shape()
shapes = append(shapes, shape)
}
if err := zr.Err(); err != nil {
t.Errorf("Error when iterating over the shapes: %v", err)
}
if err := zr.Close(); err != nil {
t.Errorf("Could not close zipreader: %v", err)
}
return shapes
}
func TestZipReader(t *testing.T) {
for prefix := range dataForReadTests {
t.Logf("Testing zipped reading for %s", prefix)
testshapeIdentity(t, prefix, getShapesZipped)
}
}
func unzipToTempDir(t *testing.T, p string) string {
td, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("%v", err)
}
zip, err := zip.OpenReader(p)
if err != nil {
t.Fatalf("%v", err)
}
defer zip.Close()
for _, f := range zip.File {
_, fn := path.Split(f.Name)
pn := filepath.Join(td, fn)
t.Logf("Uncompress: %s -> %s", f.Name, pn)
w, err := os.Create(pn)
if err != nil {
t.Fatalf("Cannot unzip %s: %v", p, err)
}
defer w.Close()
r, err := f.Open()
if err != nil {
t.Fatalf("Cannot unzip %s: %v", p, err)
}
defer r.Close()
_, err = io.Copy(w, r)
if err != nil {
t.Fatalf("Cannot unzip %s: %v", p, err)
}
}
return td
}
// TestZipReaderAttributes reads the same shapesfile twice, first directly from
// the Shp with a Reader, and, second, from a zip. It compares the fields as
// well as the shapes and the attributes. For this test, the Shapes are
// considered to be equal if their bounding boxes are equal.
func TestZipReaderAttribute(t *testing.T) {
b := "ne_110m_admin_0_countries"
skipOrDownloadNaturalEarth(t, b+".zip")
d := unzipToTempDir(t, b+".zip")
defer os.RemoveAll(d)
lr, err := Open(filepath.Join(d, b+".shp"))
if err != nil {
t.Fatal(err)
}
defer lr.Close()
zr, err := OpenZip(b + ".zip")
if os.IsNotExist(err) {
t.Skipf("Skipping test, as Natural Earth dataset wasn't found")
}
if err != nil {
t.Fatal(err)
}
defer zr.Close()
fsl := lr.Fields()
fsz := zr.Fields()
if len(fsl) != len(fsz) {
t.Fatalf("Number of attributes do not match: Wanted %d, got %d", len(fsl), len(fsz))
}
for i := range fsl {
if fsl[i] != fsz[i] {
t.Fatalf("Attribute %d (%s) does not match (%s)", i, fsl[i], fsz[i])
}
}
for zr.Next() && lr.Next() {
ln, ls := lr.Shape()
zn, zs := zr.Shape()
if ln != zn {
t.Fatalf("Sequence number wrong: Wanted %d, got %d", ln, zn)
}
if ls.BBox() != zs.BBox() {
t.Fatalf("Bounding boxes for shape #%d do not match", ln+1)
}
for i := range fsl {
la := lr.Attribute(i)
za := zr.Attribute(i)
if la != za {
t.Fatalf("Shape %d: Attribute %d (%s) are unequal: '%s' vs '%s'",
ln+1, i, fsl[i].String(), la, za)
}
}
}
if lr.Err() != nil {
t.Logf("Reader error: %v / ZipReader error: %v", lr.Err(), zr.Err())
t.FailNow()
}
}
func skipOrDownloadNaturalEarth(t *testing.T, p string) {
if _, err := os.Stat(p); os.IsNotExist(err) {
dl := false
for _, a := range os.Args {
if a == "download" {
dl = true
break
}
}
u := "http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/110m/cultural/ne_110m_admin_0_countries.zip"
if !dl {
t.Skipf("Skipped, as %s does not exist. Consider calling tests with '-args download` "+
"or download manually from '%s'", p, u)
} else {
t.Logf("Downloading %s", u)
w, err := os.Create(p)
if err != nil {
t.Fatalf("Could not create %q: %v", p, err)
}
defer w.Close()
resp, err := http.Get(u)
if err != nil {
t.Fatalf("Could not download %q: %v", u, err)
}
defer resp.Body.Close()
_, err = io.Copy(w, resp.Body)
if err != nil {
t.Fatalf("Could not download %q: %v", u, err)
}
t.Logf("Download complete")
}
}
}
func TestNaturalEarthZip(t *testing.T) {
type metaShape struct {
Attributes map[string]string
Shape
}
p := "ne_110m_admin_0_countries.zip"
skipOrDownloadNaturalEarth(t, p)
zr, err := OpenZip(p)
if err != nil {
t.Fatal(err)
}
defer zr.Close()
fs := zr.Fields()
if len(fs) != 63 {
t.Fatalf("Expected 63 columns in Natural Earth dataset, got %d", len(fs))
}
var metas []metaShape
for zr.Next() {
m := metaShape{
Attributes: make(map[string]string),
}
_, m.Shape = zr.Shape()
for n := range fs {
m.Attributes[fs[n].String()] = zr.Attribute(n)
}
metas = append(metas, m)
}
if zr.Err() != nil {
t.Fatal(zr.Err())
}
for _, m := range metas {
t.Log(m.Attributes["name"])
}
}

308
api/v1/common/tool/tool.go Normal file
View File

@ -0,0 +1,308 @@
package tool
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"time"
)
/*
判断文件或文件夹是否存在
如果返回的错误为nil,说明文件或文件夹存在
如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在
如果返回的错误为其它类型,则不确定是否在存在
*/
func PathExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}
/*
检查字符串是否在某一数组中
*/
func IsContainStr(items []string, item string) bool {
for _, eachItem := range items {
if eachItem == item {
return true
}
}
return false
}
// 调用os.MkdirAll递归创建文件夹
func CreateDir(dirPath string) {
if !isExist(dirPath) {
fmt.Println("路径不存在,创建路径", dirPath)
_ = os.MkdirAll(dirPath, os.ModePerm)
}
}
// 判断所给路径文件/文件夹是否存在(返回true是存在)
func isExist(path string) bool {
_, err := os.Stat(path) //os.Stat获取文件信息
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}
/*获取uuid*/
func GetUuid() string {
str := GetRandstring(32)
time.Sleep(time.Nanosecond)
return strings.ToLower(Md5V(str))
}
// #取得随机字符串:使用字符串拼接
func GetRandstring(lenNum int) string {
var CHARS = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}
str := strings.Builder{}
length := len(CHARS)
for i := 0; i < lenNum; i++ {
l := CHARS[rand.Intn(length)]
str.WriteString(l)
}
return str.String()
}
// 获取当前执行程序所在的绝对路径
func GetCurrentAbPathByExecutable() string {
exePath, err := os.Executable()
if err != nil {
log.Fatal(err)
}
res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
return res
}
/*结构体转map且首字母小写*/
func Struct2Map(obj interface{}) map[string]interface{} {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
var data = make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
data[strings.ToLower(string(t.Field(i).Name[0]))+t.Field(i).Name[1:]] = v.Field(i).Interface()
//data[t.Field(i).Name] = v.Field(i).Interface()
}
return data
}
func PortInUse(port int) int {
checkStatement := fmt.Sprintf("lsof -i:%d ", port)
output, _ := exec.Command("sh", "-c", checkStatement).CombinedOutput()
fmt.Println(output)
if len(output) > 0 {
return 1
}
return -1
}
/*检测端口是否在使用中*/
// 传入查询的端口号
// 返回端口号对应的进程PID若没有找到相关进程返回-1
func portInUse(portNumber int) int {
res := -1
var outBytes bytes.Buffer
sysType := runtime.GOOS
fmt.Println(sysType)
var cmdStr = ""
if sysType == "linux" {
cmdStr = fmt.Sprintf("lsof -i:%d ", portNumber)
}
//
if sysType == "windows" {
cmdStr = fmt.Sprintf("netstat -ano -p tcp | findstr %d", portNumber)
}
//checkStatement := fmt.Sprintf("lsof -i:%d ", portNumber)
fmt.Println(cmdStr)
cmd := exec.Command("cmd", "/c", cmdStr)
cmd.Stdout = &outBytes
cmd.Run()
resStr := outBytes.String()
fmt.Println(resStr)
r := regexp.MustCompile(`\s\d+\s`).FindAllString(resStr, -1)
if len(r) > 0 {
pid, err := strconv.Atoi(strings.TrimSpace(r[0]))
if err != nil {
res = -1
} else {
res = pid
}
}
return res
}
func GetUnUsePort(port int) int {
isInUse := PortInUse(port)
if isInUse != -1 {
fmt.Println("端口:" + strconv.Itoa(port) + " 被占用")
port++
port = GetUnUsePort(port)
return port
} else {
return port
}
}
// WeekIntervalTime 获取某周的开始和结束时间,week为0本周,-1上周1下周以此类推
func WeekIntervalTime(week int) (startTime, endTime string) {
now := time.Now()
offset := int(time.Monday - now.Weekday())
//周日做特殊判断 因为time.Monday = 0
if offset > 0 {
offset = -6
}
year, month, day := now.Date()
thisWeek := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
startTime = thisWeek.AddDate(0, 0, offset+7*week).Format("2006-01-02") + " 00:00:00"
endTime = thisWeek.AddDate(0, 0, offset+6+7*week).Format("2006-01-02") + " 23:59:59"
return startTime, endTime
}
// GetCurrentTime 获取当前系统时间
func GetCurrentTime() string {
return time.Now().Format("2006-01-02 15:04:05")
}
/*删除文件或文件夹*/
func RemoveFile(path string) {
if isExist(path) {
err := os.Remove(path)
if err != nil {
return
}
}
}
// 检查参数
func Md5V(str string) string {
h := md5.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}
// download file会将url下载到本地文件它会在下载时写入而不是将整个文件加载到内存中。
func DownloadFile(filepath string, url string) error {
// Get the data
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// Create the file
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
// Write the body to file
_, err = io.Copy(out, resp.Body)
return err
}
// 通用排序
// 结构体排序必须重写数组Len() Swap() Less()函数
type body_wrapper struct {
Bodys []interface{}
by func(p, q *interface{}) bool //内部Less()函数会用到
}
type SortBodyBy func(p, q *interface{}) bool //定义一个函数类型
// 数组长度Len()
func (acw body_wrapper) Len() int {
return len(acw.Bodys)
}
// 元素交换
func (acw body_wrapper) Swap(i, j int) {
acw.Bodys[i], acw.Bodys[j] = acw.Bodys[j], acw.Bodys[i]
}
// 比较函数使用外部传入的by比较函数
func (acw body_wrapper) Less(i, j int) bool {
return acw.by(&acw.Bodys[i], &acw.Bodys[j])
}
// 自定义排序字段参考SortBodyByCreateTime中的传入函数
func SortBody(bodys []interface{}, by SortBodyBy) {
sort.Sort(body_wrapper{bodys, by})
}
// 格式化时间
type LocalTime time.Time
func (t *LocalTime) MarshalJSON() ([]byte, error) {
tTime := time.Time(*t)
return []byte(fmt.Sprintf("\"%v\"", tTime.Format("2006-01-02 15:04:05"))), nil
}
// 写入文件,保存
func WriteFile(path string, base64_image_content string) (error, string) {
//b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, base64_image_content)
//if !b {
// return errors.New(""), ""
//}
base64_image_content = "data:image/png;base64," + base64_image_content
re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
allData := re.FindAllSubmatch([]byte(base64_image_content), 2)
fileType := string(allData[0][1]) //png jpeg 后缀获取
base64Str := re.ReplaceAllString(base64_image_content, "")
//date := time.Now().Format("2006-01-02")
//if ok := IsFileExist(path + "/" + date); !ok {
// os.Mkdir(path+"/"+date, 0666)
//}
curFileStr := strconv.FormatInt(time.Now().UnixNano(), 10)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
n := r.Intn(99999)
var filename = curFileStr + strconv.Itoa(n) + "." + fileType
var file = path + "/" + filename
byte, _ := base64.StdEncoding.DecodeString(base64Str)
err := ioutil.WriteFile(file, byte, 0666)
if err != nil {
log.Println(err)
}
return err, filename
}
func UploadsFile() {
}

View File

@ -0,0 +1,26 @@
package turf
import (
_ "embed"
"github.com/dop251/goja"
)
//go:embed turf.min.js
var turf_min string
//go:embed turf.js
var turf string
var Tin func(wgs84 [][]string) [][]string
var BooleanPointInPolygon func(point []float64, polygon [][]float64) bool
//// 初始化
//func init() {
// InitTurfjs()
//}
func InitTurfjs() {
vm := goja.New()
vm.RunString(turf_min + turf)
vm.ExportTo(vm.Get("Tin"), &Tin)
vm.ExportTo(vm.Get("BooleanPointInPolygon"), &BooleanPointInPolygon)
}

View File

@ -0,0 +1,26 @@
function Tin(points=[]) {
let arr = []
points.forEach(p=>{
arr.push(turf.point( [parseFloat(p[0]), parseFloat(p[1])]))
})
var tin = turf.tin(turf.featureCollection(arr));
let polylines=[]
tin.features.forEach((feature, index) => {
feature.geometry.coordinates.forEach((coordinate,) => {
polylines.push([
coordinate[0],
coordinate[1],
coordinate[2],
])
})
})
return polylines
return JSON.stringify(polylines)
}
function BooleanPointInPolygon(point,polygon=[]) {
var pt = turf.point([point[0],point[1]]);
var poly = turf.polygon([polygon]);
var scaledPoly = turf.transformScale(poly, 2);
return turf.booleanPointInPolygon(pt, poly)
}

91
api/v1/common/tool/turf/turf.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,52 @@
package tool
import (
"math"
)
var a = float64(6378137)
var b = 6356752.3142
var asqr = a * a
var bsqr = b * b
var e = math.Sqrt((asqr - bsqr) / asqr)
var eprime = math.Sqrt((asqr - bsqr) / bsqr)
func Xyz2Wgs84(X, Y, Z float64) (lng, lat, height float64) {
var p = math.Sqrt(X*X + Y*Y)
var theta = math.Atan((Z * a) / (p * b))
var sintheta = math.Sin(theta)
var costheta = math.Cos(theta)
var num = Z + eprime*eprime*b*sintheta*sintheta*sintheta
var denom = p - e*e*a*costheta*costheta*costheta
//Now calculate LLA
var latitude = math.Atan(num / denom)
var longitude = math.Atan(Y / X)
var N = getN(latitude)
var altitude = (p / math.Cos(latitude)) - N
if X < 0 && Y < 0 {
longitude = longitude - math.Pi
}
if X < 0 && Y > 0 {
longitude = longitude + math.Pi
}
return radiansToDegrees(longitude), radiansToDegrees(latitude), altitude
}
func getN(latitude float64) float64 {
var sinlatitude = math.Sin(latitude)
var denom = math.Sqrt(1 - e*e*sinlatitude*sinlatitude)
var N = a / denom
return N
}
func radiansToDegrees(radians float64) float64 {
return radians * 180 / math.Pi
}
//106.54959740614493 23.47200769358978

View File

@ -0,0 +1,113 @@
package zip
import (
"archive/zip"
"fmt"
"io"
"os"
"path"
)
func ZipFiles(filename string, files []string) error {
fmt.Println("start zip file......")
//创建输出文件目录
newZipFile, err := os.Create(filename)
if err != nil {
return err
}
defer newZipFile.Close()
//创建空的zip档案可以理解为打开zip文件准备写入
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
// Add files to zip
for _, file := range files {
if err = AddFileToZip(zipWriter, file); err != nil {
return err
}
}
return nil
}
func AddFileToZip(zipWriter *zip.Writer, filename string) error {
//打开要压缩的文件
fileToZip, err := os.Open(filename)
if err != nil {
return err
}
defer fileToZip.Close()
//获取文件的描述
info, err := fileToZip.Stat()
if err != nil {
return err
}
//FileInfoHeader返回一个根据fi填写了部分字段的Header可以理解成是将fileinfo转换成zip格式的文件信息
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filename
/*
预定义压缩算法。
archive/zip包中预定义的有两种压缩方式。一个是仅把文件写入到zip中。不做压缩。一种是压缩文件然后写入到zip中。默认的Store模式。就是只保存不压缩的模式。
Store unit16 = 0 //仅存储文件
Deflate unit16 = 8 //压缩文件
*/
header.Method = zip.Store
//创建压缩包头部信息
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
//将源复制到目标将fileToZip 写入writer 是按默认的缓冲区32k循环操作的不会将内容一次性全写入内存中,这样就能解决大文件的问题
_, err = io.Copy(writer, fileToZip)
return err
}
// Decompressor 解压
func Decompressor(zipFilePath string, targetDir string, filename string) error {
reader, err := zip.OpenReader(zipFilePath)
if nil != err {
fmt.Println(err)
return err
}
defer reader.Close()
_ = os.MkdirAll(targetDir, 0777)
names := []string{}
for _, f := range reader.File {
err := func() error {
if f.FileInfo().IsDir() {
_ = os.MkdirAll(path.Join(targetDir, f.Name), f.Mode())
return nil
}
suffix := path.Ext(f.Name)
//fmt.Println(f.Name)
//fmt.Println(path.Join(targetDir, f.Name))
writeFile, err := os.OpenFile(path.Join(targetDir, filename+suffix), os.O_WRONLY|os.O_CREATE, f.Mode())
if nil != err {
return err
}
defer writeFile.Close()
readFile, err := f.Open()
if nil != err {
return err
}
defer readFile.Close()
n, err := io.Copy(writeFile, readFile)
if nil != err {
return err
}
if false {
names = append(names, f.Name)
fmt.Printf("解压文件: %s 大小: %v", f.Name, n)
}
return nil
}()
if nil != err {
return err
}
}
return nil
}