forked from TrueCloudLab/frostfs-node
[#316] locode: Implement polygons DB based on GeoJSON polygons
Define database of Earth's polygons in GeoJSON format. Implement resolving of geo point to continent in which it is located through point-in-polygon calculation. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
d8c3a0e4f5
commit
9669afdfc7
5 changed files with 165 additions and 1 deletions
3
go.mod
3
go.mod
|
@ -20,6 +20,7 @@ require (
|
|||
github.com/nspcc-dev/neofs-crypto v0.3.0
|
||||
github.com/nspcc-dev/tzhash v1.4.0
|
||||
github.com/panjf2000/ants/v2 v2.3.0
|
||||
github.com/paulmach/orb v0.2.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.6.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
|
@ -36,7 +37,7 @@ require (
|
|||
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 // indirect
|
||||
golang.org/x/tools v0.0.0-20200123022218-593de606220b // indirect
|
||||
google.golang.org/grpc v1.29.1
|
||||
google.golang.org/protobuf v1.23.0
|
||||
google.golang.org/protobuf v1.25.0
|
||||
)
|
||||
|
||||
// Used for debug reasons
|
||||
|
|
BIN
go.sum
BIN
go.sum
Binary file not shown.
90
pkg/util/locode/db/continents/geojson/calls.go
Normal file
90
pkg/util/locode/db/continents/geojson/calls.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package continentsdb
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
locodedb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db"
|
||||
"github.com/paulmach/orb"
|
||||
"github.com/paulmach/orb/geojson"
|
||||
"github.com/paulmach/orb/planar"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const continentProperty = "Continent"
|
||||
|
||||
// PointContinent goes through all polygons, and returns continent
|
||||
// in which point is located.
|
||||
//
|
||||
// Returns locodedb.ContinentUnknown if no entry matches.
|
||||
//
|
||||
// All GeoJSON feature are parsed from file once and stored in memory.
|
||||
func (db *DB) PointContinent(point *locodedb.Point) (*locodedb.Continent, error) {
|
||||
var err error
|
||||
|
||||
db.once.Do(func() {
|
||||
err = db.init()
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planarPoint := orb.Point{point.Longitude(), point.Latitude()}
|
||||
|
||||
var continent string
|
||||
|
||||
for _, feature := range db.features {
|
||||
if multiPolygon, ok := feature.Geometry.(orb.MultiPolygon); ok {
|
||||
if planar.MultiPolygonContains(multiPolygon, planarPoint) {
|
||||
continent = feature.Properties.MustString(continentProperty)
|
||||
break
|
||||
}
|
||||
} else if polygon, ok := feature.Geometry.(orb.Polygon); ok {
|
||||
if planar.PolygonContains(polygon, planarPoint) {
|
||||
continent = feature.Properties.MustString(continentProperty)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c := continentFromString(continent)
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (db *DB) init() error {
|
||||
data, err := ioutil.ReadFile(db.path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read data file")
|
||||
}
|
||||
|
||||
features, err := geojson.UnmarshalFeatureCollection(data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not unmarshal GeoJSON feature collection")
|
||||
}
|
||||
|
||||
db.features = features.Features
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func continentFromString(c string) locodedb.Continent {
|
||||
switch c {
|
||||
default:
|
||||
return locodedb.ContinentUnknown
|
||||
case "Africa":
|
||||
return locodedb.ContinentAfrica
|
||||
case "Asia":
|
||||
return locodedb.ContinentAsia
|
||||
case "Europe":
|
||||
return locodedb.ContinentEurope
|
||||
case "North America":
|
||||
return locodedb.ContinentNorthAmerica
|
||||
case "South America":
|
||||
return locodedb.ContinentSouthAmerica
|
||||
case "Antarctica":
|
||||
return locodedb.ContinentAntarctica
|
||||
case "Australia", "Oceania":
|
||||
return locodedb.ContinentOceania
|
||||
}
|
||||
}
|
63
pkg/util/locode/db/continents/geojson/db.go
Normal file
63
pkg/util/locode/db/continents/geojson/db.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package continentsdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/paulmach/orb/geojson"
|
||||
)
|
||||
|
||||
// Prm groups the required parameters of the DB's constructor.
|
||||
//
|
||||
// All values must comply with the requirements imposed on them.
|
||||
// Passing incorrect parameter values will result in constructor
|
||||
// failure (error or panic depending on the implementation).
|
||||
type Prm struct {
|
||||
// Path to polygons of Earth's continents in GeoJSON format.
|
||||
//
|
||||
// Must not be empty.
|
||||
Path string
|
||||
}
|
||||
|
||||
// DB is a descriptor of the Earth's polygons in GeoJSON format.
|
||||
//
|
||||
// For correct operation, DB must be created
|
||||
// using the constructor (New) based on the required parameters
|
||||
// and optional components. After successful creation,
|
||||
// The DB is immediately ready to work through API.
|
||||
type DB struct {
|
||||
path string
|
||||
|
||||
once sync.Once
|
||||
|
||||
features []*geojson.Feature
|
||||
}
|
||||
|
||||
const invalidPrmValFmt = "invalid parameter %s (%T):%v"
|
||||
|
||||
func panicOnPrmValue(n string, v interface{}) {
|
||||
panic(fmt.Sprintf(invalidPrmValFmt, n, v, v))
|
||||
}
|
||||
|
||||
// New creates a new instance of the DB.
|
||||
//
|
||||
// Panics if at least one value of the parameters is invalid.
|
||||
//
|
||||
// The created DB does not require additional
|
||||
// initialization and is completely ready for work.
|
||||
func New(prm Prm, opts ...Option) *DB {
|
||||
switch {
|
||||
case prm.Path == "":
|
||||
panicOnPrmValue("Path", prm.Path)
|
||||
}
|
||||
|
||||
o := defaultOpts()
|
||||
|
||||
for i := range opts {
|
||||
opts[i](o)
|
||||
}
|
||||
|
||||
return &DB{
|
||||
path: prm.Path,
|
||||
}
|
||||
}
|
10
pkg/util/locode/db/continents/geojson/opts.go
Normal file
10
pkg/util/locode/db/continents/geojson/opts.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package continentsdb
|
||||
|
||||
// Option sets an optional parameter of DB.
|
||||
type Option func(*options)
|
||||
|
||||
type options struct{}
|
||||
|
||||
func defaultOpts() *options {
|
||||
return &options{}
|
||||
}
|
Loading…
Reference in a new issue