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/neofs-crypto v0.3.0
|
||||||
github.com/nspcc-dev/tzhash v1.4.0
|
github.com/nspcc-dev/tzhash v1.4.0
|
||||||
github.com/panjf2000/ants/v2 v2.3.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/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.6.0
|
github.com/prometheus/client_golang v1.6.0
|
||||||
github.com/spf13/cobra v1.0.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/sys v0.0.0-20201024232916-9f70ab9862d5 // indirect
|
||||||
golang.org/x/tools v0.0.0-20200123022218-593de606220b // indirect
|
golang.org/x/tools v0.0.0-20200123022218-593de606220b // indirect
|
||||||
google.golang.org/grpc v1.29.1
|
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
|
// 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