package main import ( "encoding/json" "log" "math" "net/http" "os" "strconv" "strings" "time" geo "github.com/kellydunn/golang-geo" ) var hoodDir string = "/home/fbl/Desktop/keyserver/hoods" var hoods []Hood func hoodVoronoi(lat, long float64) *Hood { var best *Hood var bestDist float64 = math.MaxFloat64 for i, hood := range hoods { if len(hood.Location) != 1 { continue } dist := math.Pow(lat-hood.Location[0].Lat, 2) + math.Pow(long-hood.Location[0].Long, 2) dist = math.Sqrt(dist) if dist < bestDist { best = &hoods[i] bestDist = dist } } return best } func hoodPoly(lat, long float64) *Hood { var matches []*Hood var best *Hood for i, hood := range hoods { if len(hood.Location) < 3 { continue } var poly geo.Polygon for _, l := range hood.Location { poly.Add(geo.NewPoint(l.Lat, l.Long)) } if !poly.IsClosed() { // TODO: validate when parsing files! log.Printf("Polygon is not closed for hood: %s", hood.HoodInfo.Name) continue } if poly.Contains(geo.NewPoint(lat, long)) { matches = append(matches, &hoods[i]) } } if len(matches) == 0 { return nil } best = matches[0] for i, hood := range matches { // TODO: prefer polygon with smallest area if hood.HoodInfo.Id < best.HoodInfo.Id { best = matches[i] } } return best } func hoodId(id uint64) *Hood { for _, hood := range hoods { if hood.HoodInfo.Id == id { return &hood } } return nil } func keyserverV2Time(w http.ResponseWriter, r *http.Request) { start := time.Now() keyserverV2(w, r) duration := time.Since(start) log.Printf("Processed request in %s", duration) } func httpHeader(w http.ResponseWriter, r *http.Request, statusCode int) { log.Printf("\"%s %s %s\" %d", r.Method, r.RequestURI, r.Proto, statusCode) w.WriteHeader(statusCode) } func keyserverV2(w http.ResponseWriter, r *http.Request) { hoodid := r.URL.Query().Get("hoodid") lat := r.URL.Query().Get("lat") long := r.URL.Query().Get("long") lon := r.URL.Query().Get("lon") var hood *Hood = hoodId(0) if lon != "" && long != "" { httpHeader(w, r, http.StatusBadRequest) return } if lon != "" { long = lon } if hoodid != "" { id, err := strconv.ParseUint(hoodid, 10, 64) if err != nil { httpHeader(w, r, http.StatusBadRequest) return } hood = hoodId(id) } if lat != "" { if long == "" { httpHeader(w, r, http.StatusBadRequest) return } // parse numbers lat := strings.Replace(lat, ",", ".", -1) long := strings.Replace(long, ",", ".", -1) latF, err := strconv.ParseFloat(lat, 64) if err != nil { httpHeader(w, r, http.StatusBadRequest) return } longF, err := strconv.ParseFloat(long, 64) if err != nil { httpHeader(w, r, http.StatusBadRequest) return } hoodP := hoodPoly(latF, longF) hood = hoodVoronoi(latF, longF) if hoodP != nil { hood = hoodP } if hood == nil { httpHeader(w, r, http.StatusInternalServerError) return } } else if long != "" { httpHeader(w, r, http.StatusBadRequest) return } if hood == nil { log.Print("No hood found") httpHeader(w, r, http.StatusNotFound) return } b, err := json.MarshalIndent(hood, "", " ") if err != nil { log.Printf("Marshaling error: %s", err) httpHeader(w, r, http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json; charset=utf-8") w.Write(b) } func parseHoods() { var newHoods []Hood var err error items, err := os.ReadDir(hoodDir) if err != nil { log.Fatalf(`Error opening dir ("%s"): %s`, hoodDir, err) } for _, item := range items { var tmp Hood if !strings.HasSuffix(item.Name(), ".json") { log.Printf("Ignoring file without .json suffix: %s", item.Name()) continue } b, err := os.ReadFile(hoodDir + "/" + item.Name()) if err != nil { log.Printf("%s: %s", item.Name(), err) // TODO } err = json.Unmarshal(b, &tmp) if err != nil { log.Printf("%s: %s", item.Name(), err) // TODO continue } newHoods = append(newHoods, tmp) } hoods = newHoods } func blank(w http.ResponseWriter, r *http.Request) { } func main() { parseHoods() http.HandleFunc("/v2/", keyserverV2Time) http.HandleFunc("/v2/hoods.php", blank) log.Fatal(http.ListenAndServe(":8080", nil)) }