地图缩放融合
This commit is contained in:
+148
-2
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -12,6 +14,34 @@ type listOptions struct {
|
||||
NodeID string
|
||||
Since *time.Time
|
||||
Until *time.Time
|
||||
MinLat *float64
|
||||
MaxLat *float64
|
||||
MinLng *float64
|
||||
MaxLng *float64
|
||||
}
|
||||
|
||||
type mapReportViewportOptions struct {
|
||||
ListOptions listOptions
|
||||
Zoom int
|
||||
Limit int
|
||||
ClusterThreshold int
|
||||
TargetCells int
|
||||
}
|
||||
|
||||
type mapReportViewportResult struct {
|
||||
Mode string
|
||||
Total int64
|
||||
Points []mapReportRecord
|
||||
Clusters []mapReportClusterRecord
|
||||
Limit int
|
||||
Zoom int
|
||||
}
|
||||
|
||||
type mapReportClusterRecord struct {
|
||||
ClusterID string
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
Count int64
|
||||
}
|
||||
|
||||
func (s *store) Ping() error {
|
||||
@@ -62,7 +92,7 @@ func (s *store) GetNodeInfo(nodeID string) (*nodeInfoRecord, error) {
|
||||
func (s *store) ListMapReports(opts listOptions) ([]mapReportRecord, error) {
|
||||
opts = normalizeListOptions(opts)
|
||||
var rows []mapReportRecord
|
||||
q := applyNodeFilters(s.db.Model(&mapReportRecord{}), opts).
|
||||
q := applyMapReportFilters(s.db.Model(&mapReportRecord{}), opts).
|
||||
Order("updated_at DESC").
|
||||
Limit(opts.Limit).
|
||||
Offset(opts.Offset)
|
||||
@@ -71,7 +101,7 @@ func (s *store) ListMapReports(opts listOptions) ([]mapReportRecord, error) {
|
||||
|
||||
func (s *store) CountMapReports(opts listOptions) (int64, error) {
|
||||
var total int64
|
||||
q := applyNodeFilters(s.db.Model(&mapReportRecord{}), opts)
|
||||
q := applyMapReportFilters(s.db.Model(&mapReportRecord{}), opts)
|
||||
return total, q.Count(&total).Error
|
||||
}
|
||||
|
||||
@@ -83,6 +113,107 @@ func (s *store) GetMapReport(nodeID string) (*mapReportRecord, error) {
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (s *store) ListMapReportViewport(opts mapReportViewportOptions) (*mapReportViewportResult, error) {
|
||||
opts = normalizeMapReportViewportOptions(opts)
|
||||
total, err := s.CountMapReports(opts.ListOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &mapReportViewportResult{Total: total, Limit: opts.Limit, Zoom: opts.Zoom}
|
||||
if total <= int64(opts.ClusterThreshold) {
|
||||
var points []mapReportRecord
|
||||
q := applyMapReportFilters(s.db.Model(&mapReportRecord{}), opts.ListOptions).
|
||||
Order("updated_at DESC").
|
||||
Limit(opts.Limit)
|
||||
if err := q.Find(&points).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Mode = "points"
|
||||
result.Points = points
|
||||
return result, nil
|
||||
}
|
||||
clusters, err := s.ListMapReportClusters(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.Mode = "clusters"
|
||||
result.Clusters = clusters
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *store) ListMapReportClusters(opts mapReportViewportOptions) ([]mapReportClusterRecord, error) {
|
||||
opts = normalizeMapReportViewportOptions(opts)
|
||||
cellSize := mapReportClusterCellSize(opts.ListOptions, opts.TargetCells)
|
||||
var rows []struct {
|
||||
LatBucket int64
|
||||
LngBucket int64
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
Count int64
|
||||
}
|
||||
q := applyMapReportFilters(s.db.Model(&mapReportRecord{}), opts.ListOptions).
|
||||
Select("CAST((latitude + 90.0) / ? AS INTEGER) AS lat_bucket, CAST((longitude + 180.0) / ? AS INTEGER) AS lng_bucket, AVG(latitude) AS latitude, AVG(longitude) AS longitude, COUNT(*) AS count", cellSize, cellSize).
|
||||
Group("lat_bucket, lng_bucket").
|
||||
Order("count DESC").
|
||||
Limit(opts.Limit)
|
||||
if err := q.Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clusters := make([]mapReportClusterRecord, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
clusters = append(clusters, mapReportClusterRecord{
|
||||
ClusterID: fmt.Sprintf("%d:%d", row.LatBucket, row.LngBucket),
|
||||
Latitude: row.Latitude,
|
||||
Longitude: row.Longitude,
|
||||
Count: row.Count,
|
||||
})
|
||||
}
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func normalizeMapReportViewportOptions(opts mapReportViewportOptions) mapReportViewportOptions {
|
||||
if opts.Limit <= 0 {
|
||||
opts.Limit = 1000
|
||||
}
|
||||
if opts.Limit > 2000 {
|
||||
opts.Limit = 2000
|
||||
}
|
||||
if opts.ClusterThreshold <= 0 {
|
||||
opts.ClusterThreshold = 500
|
||||
}
|
||||
if opts.ClusterThreshold > 5000 {
|
||||
opts.ClusterThreshold = 5000
|
||||
}
|
||||
if opts.TargetCells <= 0 {
|
||||
opts.TargetCells = 64
|
||||
}
|
||||
if opts.TargetCells > 256 {
|
||||
opts.TargetCells = 256
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func mapReportClusterCellSize(opts listOptions, targetCells int) float64 {
|
||||
latSpan := 180.0
|
||||
if opts.MinLat != nil && opts.MaxLat != nil {
|
||||
latSpan = *opts.MaxLat - *opts.MinLat
|
||||
}
|
||||
lngSpan := 360.0
|
||||
if opts.MinLng != nil && opts.MaxLng != nil {
|
||||
if *opts.MinLng <= *opts.MaxLng {
|
||||
lngSpan = *opts.MaxLng - *opts.MinLng
|
||||
} else {
|
||||
lngSpan = 180 - *opts.MinLng + *opts.MaxLng + 180
|
||||
}
|
||||
}
|
||||
span := math.Max(latSpan, lngSpan)
|
||||
cellSize := span / float64(targetCells)
|
||||
if cellSize < 0.0001 {
|
||||
cellSize = 0.0001
|
||||
}
|
||||
return cellSize
|
||||
}
|
||||
|
||||
func (s *store) DeleteNode(nodeID string) error {
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
nodeResult := tx.Where("node_id = ?", nodeID).Delete(&nodeInfoRecord{})
|
||||
@@ -113,6 +244,21 @@ func applyNodeFilters(q *gorm.DB, opts listOptions) *gorm.DB {
|
||||
return q
|
||||
}
|
||||
|
||||
func applyMapReportFilters(q *gorm.DB, opts listOptions) *gorm.DB {
|
||||
q = applyNodeFilters(q, opts)
|
||||
if opts.MinLat != nil && opts.MaxLat != nil {
|
||||
q = q.Where("latitude IS NOT NULL AND latitude >= ? AND latitude <= ?", *opts.MinLat, *opts.MaxLat)
|
||||
}
|
||||
if opts.MinLng != nil && opts.MaxLng != nil {
|
||||
if *opts.MinLng <= *opts.MaxLng {
|
||||
q = q.Where("longitude IS NOT NULL AND longitude >= ? AND longitude <= ?", *opts.MinLng, *opts.MaxLng)
|
||||
} else {
|
||||
q = q.Where("longitude IS NOT NULL AND (longitude >= ? OR longitude <= ?)", *opts.MinLng, *opts.MaxLng)
|
||||
}
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (s *store) ListTextMessages(opts listOptions) ([]textMessageRecord, error) {
|
||||
var rows []textMessageRecord
|
||||
return rows, s.listAppendRows(opts, &rows).Error
|
||||
|
||||
Reference in New Issue
Block a user