Plot city-level traffic activity from local GeoIP coordinates

This commit is contained in:
Maxim
2026-10-15 20:48:04 +03:00
parent 03b6c1f289
commit 46bd6e8be6
6 changed files with 192 additions and 26 deletions

View File

@@ -6,6 +6,7 @@ import (
"encoding/hex"
"fmt"
"io"
"math"
"net"
"net/http"
"os"
@@ -26,6 +27,7 @@ var trafficCity struct {
db *maxminddb.Reader
running bool
message string
cache map[string]TrafficCityLocation
}
func trafficCityPath() string {
@@ -49,14 +51,34 @@ func trafficCityReady() bool {
defer trafficCity.Unlock()
return trafficCity.db != nil
}
type TrafficCityLocation struct {
City string `json:"city"`
Region string `json:"region,omitempty"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Located bool `json:"located"`
}
func lookupTrafficCity(text string) (string, string) {
g := lookupTrafficCityLocation(text)
return g.City, g.Region
}
func lookupTrafficCityLocation(text string) TrafficCityLocation {
ip := net.ParseIP(text)
if ip == nil || ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || !ip.IsGlobalUnicast() || !trafficCityReady() {
return "", ""
return TrafficCityLocation{}
}
trafficCity.Lock()
defer trafficCity.Unlock()
if g, ok := trafficCity.cache[text]; ok {
return g
}
var r struct {
Location struct {
Latitude *float64 `maxminddb:"latitude"`
Longitude *float64 `maxminddb:"longitude"`
} `maxminddb:"location"`
City struct {
Names map[string]string `maxminddb:"names"`
} `maxminddb:"city"`
@@ -65,13 +87,24 @@ func lookupTrafficCity(text string) (string, string) {
} `maxminddb:"subdivisions"`
}
if trafficCity.db.Lookup(ip, &r) != nil {
return "", ""
return TrafficCityLocation{}
}
region := ""
if len(r.Subdivisions) > 0 {
region = cityName(r.Subdivisions[0].Names)
}
return cityName(r.City.Names), region
g := TrafficCityLocation{City: cityName(r.City.Names), Region: region}
if g.City != "" && r.Location.Latitude != nil && r.Location.Longitude != nil {
g.Latitude, g.Longitude = *r.Location.Latitude, *r.Location.Longitude
g.Located = !math.IsNaN(g.Latitude) && !math.IsNaN(g.Longitude) && math.Abs(g.Latitude) <= 90 && math.Abs(g.Longitude) <= 180
}
if trafficCity.cache == nil {
trafficCity.cache = map[string]TrafficCityLocation{}
}
if len(trafficCity.cache) < 32768 {
trafficCity.cache[text] = g
}
return g
}
func trafficCityRoutes(mux *http.ServeMux) {
mux.HandleFunc("/api/settings/traffic/city-database", func(w http.ResponseWriter, r *http.Request) {
@@ -178,5 +211,6 @@ func installTrafficCityFrom(source, expectedHash string) error {
trafficCity.db.Close()
}
trafficCity.db = db
trafficCity.cache = nil
return nil
}