Add location-aware automatic default bounding box
The OpenSky "Default bounding box" now follows where flying happens. A new "Automatic" picker mode (the default) resolves the live-map area from a location cascade — drone telemetry → phone GPS → browser geolocation → the user's Region country → Europe — instead of a fixed box. Manual presets and Custom coordinates still work. - Web App: new shared countries.js dataset (all countries + bbox, offline point→country); the bbox picker gains all European countries and an Automatic option (client pref prefs.autoBbox); the Region setting expands from 6 locale entries to all countries; the live map resolves the cascade each poll and sends it as ?bbox=. - API Server: the states endpoint accepts and validates a ?bbox= override (validBBox); the Web App BFF forwards the query; the hub relays new phoneLatitude/phoneLongitude telemetry to the Web App. - Fly App: reports the phone's own GPS (geolocator) alongside telemetry, used as the "your location" fallback. - API panel: the OpenSky bbox picker lists all European countries. Builds verified across web, panel, both Go modules and the Fly App APK. Region list, Automatic default and the cascade ?bbox= override verified in the browser. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
150758b0bf
commit
94f6876024
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0F1E3D" />
|
||||
<title>PilotVault · API Server</title>
|
||||
<script type="module" crossorigin src="/assets/index--4fMszzi.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CwiTp2Sb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CVh8EDzq.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -7,9 +7,34 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// validBBox reports whether s is a well-formed "lamin,lomin,lamax,lomax" bounding
|
||||
// box: four numbers, latitudes in [-90,90], longitudes in [-180,180], and min < max
|
||||
// on each axis. Used to vet the Live map's client-supplied ?bbox= override before
|
||||
// it reaches OpenSky.
|
||||
func validBBox(s string) bool {
|
||||
parts := strings.Split(s, ",")
|
||||
if len(parts) != 4 {
|
||||
return false
|
||||
}
|
||||
n := make([]float64, 4)
|
||||
for i, p := range parts {
|
||||
v, err := strconv.ParseFloat(strings.TrimSpace(p), 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
n[i] = v
|
||||
}
|
||||
laMin, loMin, laMax, loMax := n[0], n[1], n[2], n[3]
|
||||
if laMin < -90 || laMax > 90 || loMin < -180 || loMax > 180 {
|
||||
return false
|
||||
}
|
||||
return laMin < laMax && loMin < loMax
|
||||
}
|
||||
|
||||
// Integrations exposes the OpenSky plugin's settings to end users under a
|
||||
// three-layer cascade (superadmin/global → organization → user). Each of the
|
||||
// four settings resolves independently, top wins, and a blank field falls
|
||||
@@ -586,11 +611,19 @@ func (s *Server) handleOpenSkyStates(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// The Live map may request a specific area (auto cascade: drone → device →
|
||||
// region). Honour a well-formed ?bbox= override; otherwise use the resolved
|
||||
// config bbox. Malformed input is ignored rather than erroring.
|
||||
bbox := res.eff.Bbox
|
||||
if q := strings.TrimSpace(r.URL.Query().Get("bbox")); q != "" && validBBox(q) {
|
||||
bbox = q
|
||||
}
|
||||
|
||||
cfg := map[string]string{
|
||||
"clientId": res.eff.ClientID,
|
||||
"clientSecret": res.eff.ClientSecret,
|
||||
"plan": res.eff.Plan,
|
||||
"bbox": res.eff.Bbox,
|
||||
"bbox": bbox,
|
||||
"allowAnonymous": boolStr(res.allowAnon),
|
||||
}
|
||||
raw, err := s.plugins.InvokeWith(r.Context(), openSkyPlugin, cfg, "states.bbox", nil)
|
||||
|
||||
@@ -11,6 +11,10 @@ type Telemetry struct {
|
||||
Altitude *float64 `json:"altitude,omitempty"`
|
||||
Latitude *float64 `json:"latitude,omitempty"`
|
||||
Longitude *float64 `json:"longitude,omitempty"`
|
||||
// Phone's own GPS (reported by the Fly App), distinct from the drone's fix
|
||||
// above — used as a location fallback for the Web App's auto bounding box.
|
||||
PhoneLatitude *float64 `json:"phoneLatitude,omitempty"`
|
||||
PhoneLongitude *float64 `json:"phoneLongitude,omitempty"`
|
||||
VelocityX *float64 `json:"velocityX,omitempty"`
|
||||
VelocityY *float64 `json:"velocityY,omitempty"`
|
||||
VelocityZ *float64 `json:"velocityZ,omitempty"`
|
||||
@@ -100,6 +104,12 @@ func applyTelemetry(t *Telemetry, raw map[string]any) {
|
||||
if v, ok := toFloat(raw["longitude"]); ok {
|
||||
t.Longitude = &v
|
||||
}
|
||||
if v, ok := toFloat(raw["phoneLatitude"]); ok {
|
||||
t.PhoneLatitude = &v
|
||||
}
|
||||
if v, ok := toFloat(raw["phoneLongitude"]); ok {
|
||||
t.PhoneLongitude = &v
|
||||
}
|
||||
if v, ok := toFloat(raw["velocityX"]); ok {
|
||||
t.VelocityX = &v
|
||||
}
|
||||
|
||||
@@ -190,15 +190,61 @@ const BBOX_GROUPS = [
|
||||
{ value: "-56,-82,13,-34", label: "South America" },
|
||||
{ value: "-48,110,-10,180", label: "Oceania" },
|
||||
] },
|
||||
{ label: "Countries", options: [
|
||||
{ value: "49,14.1,54.9,24.2", label: "Poland" },
|
||||
{ value: "50.5,3.2,53.7,7.3", label: "Netherlands" },
|
||||
{ value: "47.2,5.8,55.1,15.1", label: "Germany" },
|
||||
// European countries (mirrors Web App/web/src/countries.js — keep in sync).
|
||||
{ label: "European countries", options: [
|
||||
{ value: "39.6,19.3,42.7,21.1", label: "Albania" },
|
||||
{ value: "42.4,1.4,42.7,1.8", label: "Andorra" },
|
||||
{ value: "46.4,9.5,49.0,17.2", label: "Austria" },
|
||||
{ value: "51.2,23.2,56.2,32.8", label: "Belarus" },
|
||||
{ value: "49.5,2.5,51.5,6.4", label: "Belgium" },
|
||||
{ value: "42.6,15.7,45.3,19.6", label: "Bosnia and Herzegovina" },
|
||||
{ value: "41.2,22.4,44.2,28.6", label: "Bulgaria" },
|
||||
{ value: "42.4,13.5,46.6,19.4", label: "Croatia" },
|
||||
{ value: "34.6,32.3,35.7,34.6", label: "Cyprus" },
|
||||
{ value: "48.6,12.1,51.1,18.9", label: "Czechia" },
|
||||
{ value: "54.6,8.1,57.8,12.7", label: "Denmark" },
|
||||
{ value: "57.5,21.8,59.7,28.2", label: "Estonia" },
|
||||
{ value: "59.8,20.6,70.1,31.6", label: "Finland" },
|
||||
{ value: "41.3,-5.2,51.1,9.6", label: "France" },
|
||||
{ value: "49.9,-8.7,59,1.8", label: "United Kingdom" },
|
||||
{ value: "35.9,-9.6,43.8,3.4", label: "Spain" },
|
||||
{ value: "47.2,5.8,55.1,15.1", label: "Germany" },
|
||||
{ value: "34.8,19.4,41.8,28.3", label: "Greece" },
|
||||
{ value: "45.7,16.1,48.6,22.9", label: "Hungary" },
|
||||
{ value: "63.3,-24.6,66.6,-13.5", label: "Iceland" },
|
||||
{ value: "51.4,-10.6,55.4,-6.0", label: "Ireland" },
|
||||
{ value: "36.6,6.6,47.1,18.6", label: "Italy" },
|
||||
{ value: "41.8,20.0,43.3,21.8", label: "Kosovo" },
|
||||
{ value: "55.7,20.9,58.1,28.2", label: "Latvia" },
|
||||
{ value: "47.0,9.4,47.3,9.6", label: "Liechtenstein" },
|
||||
{ value: "53.9,20.9,56.5,26.9", label: "Lithuania" },
|
||||
{ value: "49.4,5.7,50.2,6.5", label: "Luxembourg" },
|
||||
{ value: "35.8,14.1,36.1,14.6", label: "Malta" },
|
||||
{ value: "45.4,26.6,48.5,30.2", label: "Moldova" },
|
||||
{ value: "43.72,7.40,43.75,7.44", label: "Monaco" },
|
||||
{ value: "41.8,18.4,43.6,20.4", label: "Montenegro" },
|
||||
{ value: "50.7,3.3,53.7,7.2", label: "Netherlands" },
|
||||
{ value: "40.8,20.4,42.4,23.0", label: "North Macedonia" },
|
||||
{ value: "57.9,4.6,71.2,31.1", label: "Norway" },
|
||||
{ value: "49.0,14.1,54.9,24.2", label: "Poland" },
|
||||
{ value: "36.9,-9.5,42.2,-6.2", label: "Portugal" },
|
||||
{ value: "43.6,20.2,48.3,29.7", label: "Romania" },
|
||||
{ value: "41.2,19.6,81.9,180", label: "Russia" },
|
||||
{ value: "43.89,12.40,43.99,12.52", label: "San Marino" },
|
||||
{ value: "42.2,18.8,46.2,23.0", label: "Serbia" },
|
||||
{ value: "47.7,16.8,49.6,22.6", label: "Slovakia" },
|
||||
{ value: "45.4,13.4,46.9,16.6", label: "Slovenia" },
|
||||
{ value: "35.9,-9.4,43.8,3.4", label: "Spain" },
|
||||
{ value: "55.3,11.1,69.1,24.2", label: "Sweden" },
|
||||
{ value: "45.8,5.9,47.8,10.5", label: "Switzerland" },
|
||||
{ value: "35.8,25.7,42.3,44.8", label: "Turkey" },
|
||||
{ value: "44.4,22.1,52.4,40.2", label: "Ukraine" },
|
||||
{ value: "49.9,-8.7,60.9,1.8", label: "United Kingdom" },
|
||||
{ value: "41.900,12.445,41.908,12.458", label: "Vatican City" },
|
||||
] },
|
||||
{ label: "Other countries", options: [
|
||||
{ value: "24,-125,49.5,-66.5", label: "United States" },
|
||||
{ value: "41.7,-141,83.1,-52.6", label: "Canada" },
|
||||
{ value: "-43.6,113.3,-10.7,153.6", label: "Australia" },
|
||||
{ value: "24,122.9,45.5,145.8", label: "Japan" },
|
||||
] },
|
||||
];
|
||||
const BBOX_FLAT = BBOX_GROUPS.flatMap((g) => g.options);
|
||||
|
||||
Reference in New Issue
Block a user