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
@@ -0,0 +1,57 @@
|
||||
require "socket"
|
||||
require "json"
|
||||
|
||||
module Maptool
|
||||
# A minimal client for gpsd's JSON protocol (https://gpsd.io/gpsd_json.html).
|
||||
# Connects over TCP, asks gpsd to watch for reports, and yields a plain
|
||||
# position hash for every TPV ("time-position-velocity") report — gpsd's
|
||||
# term for a GNSS fix — for as long as the block keeps running.
|
||||
class GpsdClient
|
||||
RECONNECT_DELAY = 5 # seconds, after gpsd is unreachable or drops us
|
||||
|
||||
def initialize(host: "127.0.0.1", port: 2947)
|
||||
@host = host
|
||||
@port = port
|
||||
end
|
||||
|
||||
# Never returns under normal operation: reconnects on any I/O error
|
||||
# (gpsd not running yet, connection dropped, etc.) after a short delay,
|
||||
# so a missing/disconnected receiver is not fatal to the caller.
|
||||
def each_position
|
||||
loop do
|
||||
watch { |position| yield position }
|
||||
rescue SystemCallError, EOFError, SocketError => e
|
||||
warn "[gpsd] #{e.class}: #{e.message} — retrying in #{RECONNECT_DELAY}s"
|
||||
sleep RECONNECT_DELAY
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def watch
|
||||
socket = TCPSocket.new(@host, @port)
|
||||
socket.puts('?WATCH={"enable":true,"json":true};')
|
||||
|
||||
socket.each_line do |line|
|
||||
report = JSON.parse(line)
|
||||
next unless report["class"] == "TPV"
|
||||
next unless report["lat"] && report["lon"]
|
||||
|
||||
yield(
|
||||
lat: report["lat"],
|
||||
lon: report["lon"],
|
||||
# altHAE (height above ellipsoid) is gpsd's modern field; older
|
||||
# gpsd versions only send "alt".
|
||||
alt: report["altHAE"] || report["alt"],
|
||||
speed: report["speed"],
|
||||
track: report["track"],
|
||||
mode: report["mode"], # 0/1 = no fix, 2 = 2D, 3 = 3D
|
||||
eph: report["eph"], # estimated horizontal position error, metres
|
||||
time: report["time"],
|
||||
)
|
||||
end
|
||||
ensure
|
||||
socket&.close
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user