168 lines
3.8 KiB
Go
168 lines
3.8 KiB
Go
package openname
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/csv"
|
|
"errors"
|
|
"io"
|
|
"strconv"
|
|
|
|
"github.com/dhconnelly/rtreego"
|
|
)
|
|
|
|
type Record struct {
|
|
ID string
|
|
NamesUri string
|
|
Name string
|
|
NameLang string
|
|
AltName string
|
|
AltNameLang string
|
|
Type string
|
|
LocalType string
|
|
GeomX float64
|
|
GeomY float64
|
|
MostDetailViewRes float64
|
|
LeastDetailViewRes float64
|
|
MbrXMin float64
|
|
MbrYMin float64
|
|
MbrXMax float64
|
|
MbrYMax float64
|
|
PostcodeDistrict string
|
|
PostcodeDistrictUri string
|
|
PopulatedPlace string
|
|
PopulatedPlaceUri string
|
|
PopulatedPlaceType string
|
|
DistrictBorough string
|
|
DistrictBoroughUri string
|
|
DistrictBoroughType string
|
|
CountyUnitary string
|
|
CountyUnitaryUri string
|
|
CountyUnitaryType string
|
|
Region string
|
|
RegionUri string
|
|
Country string
|
|
CountryUri string
|
|
RelativeSpatialObject string
|
|
SameAsDbpedia string
|
|
SameAsGeonames string
|
|
}
|
|
|
|
func (r *Record) Bounds() *rtreego.Rect {
|
|
p := rtreego.Point{r.MbrXMin, r.MbrYMin}
|
|
rect, err := rtreego.NewRect(p, []float64{r.MbrXMax - r.MbrXMin, r.MbrYMax - r.MbrYMin})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return rect
|
|
}
|
|
|
|
func (r *Record) Area() float64 {
|
|
return (r.MbrXMax - r.MbrXMin) * (r.MbrYMax - r.MbrYMin)
|
|
}
|
|
|
|
type Scanner struct {
|
|
csvReader *csv.Reader
|
|
nextRecord *Record
|
|
err error
|
|
}
|
|
|
|
func NewScanner(r io.Reader) (*Scanner, error) {
|
|
br := bufio.NewReader(r)
|
|
err := skipBOM(br)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Scanner{csvReader: csv.NewReader(br)}, nil
|
|
}
|
|
|
|
var BOM = [3]byte{0xef, 0xbb, 0xbf}
|
|
|
|
func skipBOM(br *bufio.Reader) error {
|
|
xs, err := br.Peek(3)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if xs[0] == BOM[0] && xs[1] == BOM[1] && xs[2] == BOM[2] {
|
|
br.Discard(3)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Scanner) Scan() bool {
|
|
rawRecord, err := s.csvReader.Read()
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
return false
|
|
}
|
|
s.err = err
|
|
return false
|
|
}
|
|
s.nextRecord, err = parseRecord(rawRecord)
|
|
if err != nil {
|
|
s.err = err
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *Scanner) Err() error {
|
|
return s.err
|
|
}
|
|
|
|
func (s *Scanner) Record() *Record {
|
|
return s.nextRecord
|
|
}
|
|
|
|
func parseRecord(xs []string) (*Record, error) {
|
|
if len(xs) != 34 {
|
|
return nil, csv.ErrFieldCount
|
|
}
|
|
record := Record{
|
|
ID: xs[0],
|
|
NamesUri: xs[1],
|
|
Name: xs[2],
|
|
NameLang: xs[3],
|
|
AltName: xs[4],
|
|
AltNameLang: xs[5],
|
|
Type: xs[6],
|
|
LocalType: xs[7],
|
|
GeomX: 0,
|
|
GeomY: 0,
|
|
MostDetailViewRes: 0,
|
|
LeastDetailViewRes: 0,
|
|
MbrXMin: 0,
|
|
MbrYMin: 0,
|
|
MbrXMax: 0,
|
|
MbrYMax: 0,
|
|
PostcodeDistrict: xs[16],
|
|
PostcodeDistrictUri: xs[17],
|
|
PopulatedPlace: xs[18],
|
|
PopulatedPlaceUri: xs[19],
|
|
PopulatedPlaceType: xs[20],
|
|
DistrictBorough: xs[21],
|
|
DistrictBoroughUri: xs[22],
|
|
DistrictBoroughType: xs[23],
|
|
CountyUnitary: xs[24],
|
|
CountyUnitaryUri: xs[25],
|
|
CountyUnitaryType: xs[26],
|
|
Region: xs[27],
|
|
RegionUri: xs[28],
|
|
Country: xs[29],
|
|
CountryUri: xs[30],
|
|
RelativeSpatialObject: xs[31],
|
|
SameAsDbpedia: xs[32],
|
|
SameAsGeonames: xs[33],
|
|
}
|
|
|
|
for i, p := range []*float64{&record.GeomX, &record.GeomY, &record.MostDetailViewRes, &record.LeastDetailViewRes, &record.MbrXMin, &record.MbrYMin, &record.MbrXMax, &record.MbrYMax} {
|
|
s := xs[i+8]
|
|
if s != "" {
|
|
var err error
|
|
*p, err = strconv.ParseFloat(s, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
return &record, nil
|
|
}
|