Upload GPX files into a folder hierarchy (traversal-safe path resolution against the store root), browse them in a tree panel, and toggle each one's waypoints/tracks on the map. Waypoints render as clickable points showing name, description and time; bare URLs in the description are turned into safe links. GPX layers survive switching between the Online/Offline base map, same as the radar overlay. Adds rexml as an explicit dependency — it moved to a bundled gem in this Ruby version and isn't picked up by Bundler otherwise.
68 lines
1.8 KiB
Ruby
68 lines
1.8 KiB
Ruby
require "sinatra/base"
|
|
require "sinatra/json"
|
|
require_relative "lib/maptool/gpx"
|
|
require_relative "lib/maptool/gpx_store"
|
|
|
|
module Maptool
|
|
class App < Sinatra::Base
|
|
set :public_folder, File.join(__dir__, "public")
|
|
set :views, File.join(__dir__, "views")
|
|
|
|
OFFLINE_PMTILES_PATH = File.join(__dir__, "data", "offline.pmtiles")
|
|
GPX_STORE = GpxStore.new(File.join(__dir__, "data", "gpx"))
|
|
|
|
get "/" do
|
|
erb :index
|
|
end
|
|
|
|
get "/api/health" do
|
|
json status: "ok"
|
|
end
|
|
|
|
get "/api/offline-tiles" do
|
|
json available: File.exist?(OFFLINE_PMTILES_PATH)
|
|
end
|
|
|
|
# Served as a plain range-request-capable file, not decoded server-side:
|
|
# pmtiles.js reads the archive's directory and fetches only the byte
|
|
# ranges it needs for the tiles currently on screen.
|
|
get "/tiles/offline.pmtiles" do
|
|
halt 404 unless File.exist?(OFFLINE_PMTILES_PATH)
|
|
|
|
send_file OFFLINE_PMTILES_PATH, type: "application/octet-stream"
|
|
end
|
|
|
|
get "/api/gpx/tree" do
|
|
json GPX_STORE.tree
|
|
end
|
|
|
|
post "/api/gpx" do
|
|
file = params[:file]
|
|
halt 400, json(error: "file is required") unless file && file[:tempfile]
|
|
|
|
begin
|
|
path = GPX_STORE.save(folder: params[:folder].to_s, filename: file[:filename], io: file[:tempfile])
|
|
rescue GpxStore::InvalidPath
|
|
halt 400, json(error: "invalid folder")
|
|
end
|
|
|
|
json path: path, tree: GPX_STORE.tree
|
|
end
|
|
|
|
get "/api/gpx/file/*" do
|
|
begin
|
|
path = GPX_STORE.resolve_file(params[:splat].first)
|
|
rescue GpxStore::InvalidPath
|
|
halt 400
|
|
end
|
|
halt 404 unless File.file?(path)
|
|
|
|
begin
|
|
json Gpx.to_geojson(path)
|
|
rescue Gpx::ParseError
|
|
halt 422, json(error: "could not parse GPX file")
|
|
end
|
|
end
|
|
end
|
|
end
|