Redesign UI as an OsmAnd-style sidebar, add live GNSS position via gpsd
Restructure the map controls from MapLibre corner buttons into a left sidebar of icons with flyouts/panels (radar, GPX, GNSS), matching OsmAnd's layout more closely: - Base map and GPX selection are now full-height side panels docked next to the sidebar (not small dropdown flyouts or a right-docked panel), opening/closing exclusively via a small shared event bus (panels.js) so only one is ever open at a time. - The GPX panel's upload/new-folder controls are now icon buttons in the header instead of an always-visible dropzone and form; dragging a GPX file onto the tree, a specific folder, or straight onto the map itself all upload it. - New: Maptool::GpsdClient (lib/maptool/gpsd_client.rb) streams live GNSS fixes from a local gpsd daemon over Server-Sent Events (/api/gnss/stream) to a marker on the map (public/js/gnss.js) — one background gpsd connection shared by every browser tab. - The online map style (public/js/osmand-style.js) is now styled to resemble OsmAnd's own rendering, applied to both the online and offline (PMTiles) styles. Also adds a GeolocateControl (browser geolocation) next to the zoom controls.
This commit is contained in:
1 parent
008e034932
commit
87df90f75c
14 files changed
+939
-200
No files matched your search
@@ -2,6 +2,7 @@ require "sinatra/base"
|
||||
require "sinatra/json"
|
||||
require_relative "lib/maptool/gpx"
|
||||
require_relative "lib/maptool/gpx_store"
|
||||
require_relative "lib/maptool/gpsd_client"
|
||||
|
||||
module Maptool
|
||||
class App < Sinatra::Base
|
||||
@@ -11,6 +12,34 @@ module Maptool
|
||||
OFFLINE_PMTILES_PATH = File.join(__dir__, "data", "offline.pmtiles")
|
||||
GPX_STORE = GpxStore.new(File.join(__dir__, "data", "gpx"))
|
||||
|
||||
# Live GNSS position, polled by the frontend from /api/gnss/position. A
|
||||
# single background thread reads gpsd (if it's running — harmless retry
|
||||
# loop if not, see GpsdClient) and keeps this updated; polling instead
|
||||
# of push (SSE/WebSocket) is deliberate — gpsd itself only reports at
|
||||
# ~1Hz, so there is nothing to gain from a push channel, and it avoids
|
||||
# Sinatra's `stream(:keep_open)` helper, which — absent an EventMachine
|
||||
# reactor (i.e. under plain threaded Puma, as here) — busy-loops calling
|
||||
# its block repeatedly instead of waiting for external writes; fine for
|
||||
# a single blocking per-message write, not for "register once, get
|
||||
# pushed to later" like this.
|
||||
# A fix is only reported back as "available" for this long after it
|
||||
# last arrived — otherwise a receiver that's lost its fix, or been
|
||||
# unplugged, would keep showing its last position forever as if it
|
||||
# were still live.
|
||||
GNSS_STALE_AFTER = 10 # seconds
|
||||
|
||||
GNSS_LATEST = {}
|
||||
GNSS_LATEST_MUTEX = Mutex.new
|
||||
|
||||
Thread.new do
|
||||
GpsdClient.new.each_position do |position|
|
||||
GNSS_LATEST_MUTEX.synchronize do
|
||||
GNSS_LATEST.replace(position)
|
||||
GNSS_LATEST[:received_at] = Time.now.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
get "/" do
|
||||
erb :index
|
||||
end
|
||||
@@ -109,5 +138,13 @@ module Maptool
|
||||
|
||||
json path: path, tree: GPX_STORE.tree
|
||||
end
|
||||
|
||||
get "/api/gnss/position" do
|
||||
GNSS_LATEST_MUTEX.synchronize do
|
||||
received_at = GNSS_LATEST[:received_at]
|
||||
fresh = received_at && (Time.now.to_f - received_at) < GNSS_STALE_AFTER
|
||||
json(fresh ? GNSS_LATEST.merge(available: true) : { available: false })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user