#!/usr/bin/env ruby # frozen_string_literal: true # Speaks gpsd's own JSON wire protocol (https://gpsd.io/gpsd_json.html) # directly on gpsd's default port — GpsdClient (lib/maptool/gpsd_client.rb) # connects to it exactly as it would to real gpsd, so no app code or config # changes are needed. Unlike docs/development.adoc's fake-NMEA-source # example, this doesn't need gpsd itself installed/running at all, and # actually moves (a fixed point isn't much of a "GNSS position" to look # at) — drives a loop around a center point at a driving-ish pace, one TPV # report per second like a real receiver. # # Usage: bin/gnss_simulator.rb [lat] [lon] [radius_m] [port] require "socket" require "json" $stdout.sync = true LAT = (ARGV[0] || 48.1486).to_f LON = (ARGV[1] || 17.1077).to_f RADIUS_M = (ARGV[2] || 300).to_f PORT = (ARGV[3] || 2947).to_i LOOP_SECONDS = 120 # time to complete one full loop EARTH_RADIUS_M = 6_371_000 def point_on_loop(center_lat, center_lon, radius_m, angle_rad) # Flat-plane offset, not geodesic — fine at this radius (metres, not km), # same reasoning as measure.js's own approximations at this scale. lat_offset = (radius_m * Math.sin(angle_rad)) / EARTH_RADIUS_M lon_offset = (radius_m * Math.cos(angle_rad)) / (EARTH_RADIUS_M * Math.cos(center_lat * Math::PI / 180)) [center_lat + lat_offset * 180 / Math::PI, center_lon + lon_offset * 180 / Math::PI] end server = TCPServer.new("127.0.0.1", PORT) puts "[gnss-simulator] driving a #{RADIUS_M.to_i}m loop around #{LAT},#{LON} — listening on 127.0.0.1:#{PORT}" loop do socket = server.accept puts "[gnss-simulator] client connected" start = Time.now begin loop do elapsed = Time.now - start angle = (elapsed / LOOP_SECONDS) * 2 * Math::PI lat, lon = point_on_loop(LAT, LON, RADIUS_M, angle) # Bearing of travel: tangent to the circle, 90 degrees ahead of the # radius vector at this point. track = (angle * 180 / Math::PI + 90) % 360 speed = (2 * Math::PI * RADIUS_M) / LOOP_SECONDS # m/s, constant pace report = { class: "TPV", mode: 3, lat: lat, lon: lon, altHAE: 150.0, speed: speed, track: track, eph: 3.0, time: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ"), } socket.puts(JSON.generate(report)) sleep 1 end rescue Errno::EPIPE, IOError puts "[gnss-simulator] client disconnected" ensure socket.close end end