From 59516714d1f4cb601af4f2f345eed87fc2ddae4c Mon Sep 17 00:00:00 2001 From: George Bartolomey Date: Sun, 15 Sep 2024 12:00:30 +0300 Subject: [PATCH] [#14] Use quadtree to find continent Signed-off-by: George Bartolomey --- pkg/locode/db/continents/geojson/calls.go | 64 ++++++++++++++++++----- pkg/locode/db/continents/geojson/db.go | 3 ++ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/pkg/locode/db/continents/geojson/calls.go b/pkg/locode/db/continents/geojson/calls.go index b5a58d1..a766cbd 100644 --- a/pkg/locode/db/continents/geojson/calls.go +++ b/pkg/locode/db/continents/geojson/calls.go @@ -8,6 +8,7 @@ import ( "github.com/paulmach/orb" "github.com/paulmach/orb/geojson" "github.com/paulmach/orb/planar" + "github.com/paulmach/orb/quadtree" ) const continentProperty = "Continent" @@ -36,22 +37,24 @@ func (db *DB) PointContinent(point *locodedb.Point) (*locodedb.Continent, error) minDst float64 ) - for _, feature := range db.features { - if multiPolygon, ok := feature.Geometry.(orb.MultiPolygon); ok { - if planar.MultiPolygonContains(multiPolygon, planarPoint) { + pointer := db.tree.Matching(planarPoint, func(p orb.Pointer) bool { + return planar.PolygonContains( + p.(*geojson.Feature).Geometry.(orb.Polygon), + planarPoint, + ) + }) + + if pointer != nil { + continent = pointer.(*geojson.Feature).Properties.MustString(continentProperty) + } + + if continent == "" { + for _, feature := range db.features { + distance := planar.DistanceFrom(feature.Geometry, planarPoint) + if minDst == 0 || minDst > distance { + minDst = distance 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 - } - } - distance := planar.DistanceFrom(feature.Geometry, planarPoint) - if minDst == 0 || minDst > distance { - minDst = distance - continent = feature.Properties.MustString(continentProperty) } } @@ -73,6 +76,39 @@ func (db *DB) init() error { db.features = features.Features + err = db.buildQuadtree() + if err != nil { + return fmt.Errorf("could not build quadtree: %w", err) + } + + return nil +} + +func (db *DB) buildQuadtree() error { + db.tree = quadtree.New(orb.Bound{ + Min: orb.Point{-180, -180}, + Max: orb.Point{180, 180}, + }) + + for _, feature := range db.features { + var multiPolygon orb.MultiPolygon + + if polygon, ok := feature.Geometry.(orb.Polygon); ok { + multiPolygon = append(multiPolygon, polygon) + } else { + multiPolygon = feature.Geometry.(orb.MultiPolygon) + } + + for _, polygon := range multiPolygon { + newFeature := geojson.NewFeature(polygon) + newFeature.Properties = feature.Properties.Clone() + err := db.tree.Add(newFeature) + if err != nil { + return err + } + } + } + return nil } diff --git a/pkg/locode/db/continents/geojson/db.go b/pkg/locode/db/continents/geojson/db.go index ee43bd8..244da02 100644 --- a/pkg/locode/db/continents/geojson/db.go +++ b/pkg/locode/db/continents/geojson/db.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/paulmach/orb/geojson" + "github.com/paulmach/orb/quadtree" ) // Prm groups the required parameters of the DB's constructor. @@ -31,6 +32,8 @@ type DB struct { once sync.Once features []*geojson.Feature + + tree *quadtree.Quadtree } const invalidPrmValFmt = "invalid parameter %s (%T):%v"