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:
randy committed 2026-08-25 19:39:13 +02:00
1 parent 008e034932
commit 87df90f75c
14 files changed
+939 -200

No files matched your search

+48
View File
@@ -42,3 +42,51 @@ build one locally:
Once the file is in place, `GET /api/offline-tiles` reports
`available: true` and the map defaults to the offline style.
== GNSS position (gpsd)
The "GNSS" control needs https://gpsd.io[gpsd] running and pointed at
a receiver (`gpsd /dev/ttyUSB0`, or whatever device the receiver shows
up as). On openSUSE, `gpsd` is a package with systemd units
(`gpsd.service` / `gpsd.socket`); on other distributions, check your
package manager. No app-side configuration is needed — it just
connects to `127.0.0.1:2947`, gpsd's default port.
To test without hardware, feed gpsd a fake NMEA stream over TCP
instead of a real serial device — gpsd accepts a `tcp://host:port`
source:
[source,shell]
----
# terminal 1: a TCP server repeating one NMEA GGA/RMC sentence pair
python3 - <<'PY' 5001
import socket, sys, time
def checksum(s):
cs = 0
for ch in s:
cs ^= ord(ch)
return f"{cs:02X}"
def sentence(body):
return f"${body}*{checksum(body)}\r\n"
port = int(sys.argv[1])
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("127.0.0.1", port))
srv.listen(1)
conn, _ = srv.accept()
gga = "GPGGA,123519,4808.916,N,01706.462,E,1,08,0.9,140.4,M,46.9,M,,"
rmc = "GPRMC,123519,A,4808.916,N,01706.462,E,000.5,084.4,230824,003.1,W"
while True:
conn.sendall((sentence(gga) + sentence(rmc)).encode())
time.sleep(1)
PY
# terminal 2: point gpsd at it instead of a real device
gpsd -N -n tcp://127.0.0.1:5001
----
`GET /api/gnss/position` should then report the fake fix (Bratislava
Castle, in the sentences above) within a couple of seconds.