Add filters to opennames processing. Filter out suburban areas.
This commit is contained in:
parent
9296c8441e
commit
2f9e0e66e3
5 changed files with 112 additions and 29 deletions
33
cmd/localtypes/main.go
Normal file
33
cmd/localtypes/main.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ray1729/gpx-utils/pkg/openname"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
log.Fatal("Usage: %s OPNAME_CSV_ZIP", os.Args[0])
|
||||||
|
}
|
||||||
|
var records []*openname.Record
|
||||||
|
openname.ProcessFile(
|
||||||
|
os.Args[1],
|
||||||
|
func(r *openname.Record) error {
|
||||||
|
records = append(records, r)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
openname.FilterType("populatedPlace"),
|
||||||
|
openname.FilterWithinRadius(544945, 258410, 20000),
|
||||||
|
)
|
||||||
|
sort.Slice(records, func(i, j int) bool {
|
||||||
|
return records[i].Name < records[j].Name
|
||||||
|
})
|
||||||
|
for _, r := range records {
|
||||||
|
fmt.Printf("%s,%s\n", r.Name, r.LocalType)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/gob"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ray1729/gpx-utils/pkg/openname"
|
"github.com/ray1729/gpx-utils/pkg/openname"
|
||||||
|
"github.com/ray1729/gpx-utils/pkg/placenames"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -12,7 +14,28 @@ func main() {
|
||||||
if len(os.Args) != 3 {
|
if len(os.Args) != 3 {
|
||||||
log.Fatal("Usage: %s INFILE OUTFILE", os.Args[0])
|
log.Fatal("Usage: %s INFILE OUTFILE", os.Args[0])
|
||||||
}
|
}
|
||||||
if err := openname.Save(os.Args[1], os.Args[2]); err != nil {
|
wc, err := os.OpenFile(os.Args[2], os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer wc.Close()
|
||||||
|
enc := gob.NewEncoder(wc)
|
||||||
|
err = openname.ProcessFile(
|
||||||
|
os.Args[1],
|
||||||
|
func(r *openname.Record) error {
|
||||||
|
b := placenames.NamedBoundary{
|
||||||
|
Name: r.Name,
|
||||||
|
Xmin: r.MbrXMin,
|
||||||
|
Ymin: r.MbrYMin,
|
||||||
|
Xmax: r.MbrXMax,
|
||||||
|
Ymax: r.MbrYMax}
|
||||||
|
return enc.Encode(b)
|
||||||
|
},
|
||||||
|
openname.FilterType("populatedPlace"),
|
||||||
|
openname.FilterLocalType("Suburban Area").Complement(),
|
||||||
|
openname.FilterAreaGt(0),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,49 @@ package openname
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ray1729/gpx-utils/pkg/placenames"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Handler func(*Record) error
|
||||||
|
|
||||||
|
type Filter func(*Record) bool
|
||||||
|
|
||||||
|
func (f Filter) Complement() Filter {
|
||||||
|
return func(r *Record) bool {
|
||||||
|
return !f(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterType(t string) Filter {
|
||||||
|
return func(r *Record) bool {
|
||||||
|
return r.Type == t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterLocalType(t string) Filter {
|
||||||
|
return func(r *Record) bool {
|
||||||
|
return r.LocalType == t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterWithinRadius(x, y, radius float64) Filter {
|
||||||
|
return func(r *Record) bool {
|
||||||
|
dx := x - r.GeomX
|
||||||
|
dy := y - r.GeomY
|
||||||
|
return math.Sqrt(dx*dx+dy*dy) <= radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterAreaGt(a float64) Filter {
|
||||||
|
return func(r *Record) bool {
|
||||||
|
return r.Area() > a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ProcessFile reads the compressed OS Open Names data set and calls the handler for each record.
|
// ProcessFile reads the compressed OS Open Names data set and calls the handler for each record.
|
||||||
func ProcessFile(filename string, handler func(*Record) error) error {
|
func ProcessFile(filename string, handler Handler, filters ...Filter) error {
|
||||||
r, err := zip.OpenReader(filename)
|
r, err := zip.OpenReader(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error opening %s for reading: %v", filename, err)
|
return fmt.Errorf("error opening %s for reading: %v", filename, err)
|
||||||
|
@ -33,10 +66,11 @@ func ProcessFile(filename string, handler func(*Record) error) error {
|
||||||
}
|
}
|
||||||
for s.Scan() {
|
for s.Scan() {
|
||||||
r := s.Record()
|
r := s.Record()
|
||||||
if r.Type == "populatedPlace" && r.MbrXMax != r.MbrXMin && r.MbrYMax != r.MbrYMin {
|
if wanted := applyFilters(r, filters); !wanted {
|
||||||
if err := handler(r); err != nil {
|
continue
|
||||||
return err
|
}
|
||||||
}
|
if err := handler(r); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = s.Err(); err != nil {
|
if err = s.Err(); err != nil {
|
||||||
|
@ -48,22 +82,11 @@ func ProcessFile(filename string, handler func(*Record) error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save processes the OS OpenNames zip file and outputs bounded places in gob format.
|
func applyFilters(r *Record, filters []Filter) bool {
|
||||||
func Save(inFile string, outFile string) error {
|
for _, f := range filters {
|
||||||
wc, err := os.OpenFile(outFile, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
|
if !f(r) {
|
||||||
if err != nil {
|
return false
|
||||||
return err
|
}
|
||||||
}
|
}
|
||||||
defer wc.Close()
|
return true
|
||||||
enc := gob.NewEncoder(wc)
|
|
||||||
err = ProcessFile(inFile, func(r *Record) error {
|
|
||||||
b := placenames.NamedBoundary{
|
|
||||||
Name: r.Name,
|
|
||||||
Xmin: r.MbrXMin,
|
|
||||||
Ymin: r.MbrYMin,
|
|
||||||
Xmax: r.MbrXMax,
|
|
||||||
Ymax: r.MbrYMax}
|
|
||||||
return enc.Encode(b)
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,10 @@ func (r *Record) Bounds() *rtreego.Rect {
|
||||||
return rect
|
return rect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Record) Area() float64 {
|
||||||
|
return (r.MbrXMax - r.MbrXMin) * (r.MbrYMax - r.MbrYMin)
|
||||||
|
}
|
||||||
|
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
csvReader *csv.Reader
|
csvReader *csv.Reader
|
||||||
nextRecord *Record
|
nextRecord *Record
|
||||||
|
|
BIN
pkg/placenames/placenames.bin
(Stored with Git LFS)
BIN
pkg/placenames/placenames.bin
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in a new issue