geography/handlers/admin/bleve.go

153 lines
4.0 KiB
Go

package admin
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"sync"
"github.com/blevesearch/bleve/v2"
"github.com/blevesearch/bleve/v2/geo"
"github.com/paulmach/orb/geojson"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
)
type AdminBleveIndex struct {
Index bleve.Index
Data map[string]*geojson.Feature
}
func NewAdminBleveIndex(cfg *viper.Viper) (*AdminBleveIndex, error) {
file := cfg.GetString("storage.index.bleve.file")
if file == "" {
return nil, errors.New("issue in config : storage.index.beleve.file missing")
}
index, err := bleve.Open(file)
if err != nil {
log.Info().Msg("Bleve index does not exist : creating")
mapping := bleve.NewIndexMapping()
docMapping := bleve.NewDocumentMapping()
docMapping.AddFieldMappingsAt("geometry", bleve.NewGeoShapeFieldMapping())
// docMapping.AddFieldMappingsAt("properties.nom", bleve.NewTextFieldMapping())
mapping.DefaultMapping = docMapping
index, err := bleve.New(file, mapping)
if err != nil {
return nil, fmt.Errorf("issue creating index : %v", err)
}
indexstorage := &AdminBleveIndex{
Index: index,
Data: map[string]*geojson.Feature{},
}
err = indexstorage.IndexData(cfg.GetStringMapString("data.layers"))
return indexstorage, nil
}
return &AdminBleveIndex{
Index: index,
Data: map[string]*geojson.Feature{},
}, nil
}
func (b *AdminBleveIndex) IndexData(datalayers map[string]string) error {
log.Info().Msg("indexing data")
var wg sync.WaitGroup
var errs chan error
for layer, url := range datalayers {
wg.Add(1)
go func(errs chan error) {
defer wg.Done()
resp, err := http.Get(url)
if err != nil {
log.Error().Err(err).Msg("get request failed")
return
}
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Error().Err(err).Msg("cound not read response")
return
}
data, err := geojson.UnmarshalFeatureCollection(body)
if err != nil {
log.Error().Err(err).Msg("could not read input data")
return
}
for _, feature := range data.Features {
if err = b.Store(layer, feature); err != nil {
log.Error().Err(err).Msg("could not store data")
}
}
}(errs)
}
wg.Wait()
if nb, err := b.Index.DocCount(); err == nil {
log.Info().Uint64("number of documents", nb).Msg("indexed all data")
}
return nil
}
func (b *AdminBleveIndex) Store(layer string, feature *geojson.Feature) error {
id := fmt.Sprintf("%s/%s", layer, feature.Properties.MustString("code"))
log.Debug().Str("id", id).Str("name", feature.Properties.MustString("nom", "")).Msg("store data")
f, err := feature.MarshalJSON()
if err != nil {
log.Error().Err(err).Msg("geojson to json bytes issue")
return err
}
var doc map[string]any
if err = json.Unmarshal(f, &doc); err != nil {
log.Error().Err(err).Msg("json reading issue")
return err
}
err = b.Index.Index(id, doc)
if err != nil {
log.Error().Err(err).Msg("error adding to index")
}
b.Data[id] = feature
return nil
}
func (b *AdminBleveIndex) GeoSearch(f *geojson.Feature) (map[string][]*geojson.Feature, error) {
coordinates := []float64{f.Point().Lon(), f.Point().Lat()}
query, err := bleve.NewGeoShapeQuery(
[][][][]float64{{{coordinates}}},
geo.PointType,
"contains",
)
if err != nil {
return nil, fmt.Errorf("could not create query : %v", err)
}
query.SetField("geometry")
search := bleve.NewSearchRequest(query)
log.Debug().Any("query", query).Msg("query")
res, err := b.Index.Search(search)
if err != nil {
return nil, fmt.Errorf("issue in search : %v", err)
}
log.Debug().Any("result", res).Msg("got result")
for _, r := range res.Hits {
log.Debug().Any("region", b.Data[r.ID].Properties.MustString("nom")).Str("type", b.Data[r.ID].Geometry.GeoJSONType()).Msg("hit")
}
return map[string][]*geojson.Feature{}, nil
}
func (s *AdminBleveIndex) Find(layer, id string) (*geojson.Feature, error) {
result, ok := s.Data[fmt.Sprintf("%s/%s", layer, id)]
if !ok || result == nil {
return nil, errors.New("admin not found")
}
return result, nil
}