Files
maptool/lib/maptool/gpx.rb
T
randy 06ea8ea141 Add file-kind icons to the GPX tree, fix drop into an empty panel
Each tree entry now shows a small waypoint/track icon based on a
cheap substring check for <wpt>/<trk> (Maptool::Gpx.kinds) rather than
a full REXML parse, since re-parsing a several-thousand-waypoint file
on every tree fetch would make the panel noticeably slower to open.

Also fix a real drop-target bug: .gpx-tree had no flex:1, so a
short/empty tree left blank panel space below it that wasn't part of
the drag & drop target — dropping a file there silently did nothing.
2026-08-25 22:38:41 +02:00

81 lines
2.6 KiB
Ruby

require "rexml/document"
module Maptool
# Converts a GPX file to a GeoJSON FeatureCollection: waypoints (<wpt>)
# become Point features, tracks (<trk>) become MultiLineString features
# (one line per <trkseg>). Routes (<rte>) are not handled — GPX in
# practice is either points or recorded tracks for this app.
module Gpx
class ParseError < StandardError; end
# Cheap content-type check for the file tree's icons: a plain substring
# search rather than a full REXML parse, since some uploaded files (e.g.
# a several-thousand-waypoint POI export) are large enough that parsing
# the whole DOM just to answer "does it have any of these tags" would
# make every tree fetch noticeably slower.
def self.kinds(path)
content = File.read(path)
kinds = []
kinds << "waypoint" if content.include?("<wpt")
kinds << "track" if content.include?("<trk")
kinds
end
def self.to_geojson(path)
root = REXML::Document.new(File.read(path)).root
raise ParseError, "not a GPX file" unless root && root.name == "gpx"
features = []
root.each_element("wpt") { |wpt| features << waypoint_feature(wpt) }
root.each_element("trk") { |trk| features << track_feature(trk) }
{ "type" => "FeatureCollection", "features" => features }
rescue REXML::ParseException => e
raise ParseError, e.message
end
def self.waypoint_feature(wpt)
{
"type" => "Feature",
"geometry" => { "type" => "Point", "coordinates" => coordinate(wpt) },
"properties" => {
"kind" => "waypoint",
"name" => child_text(wpt, "name"),
"description" => child_text(wpt, "desc"),
"time" => child_text(wpt, "time"),
},
}
end
private_class_method :waypoint_feature
def self.track_feature(trk)
lines = []
trk.each_element("trkseg") do |seg|
points = []
seg.each_element("trkpt") { |pt| points << coordinate(pt) }
lines << points unless points.empty?
end
{
"type" => "Feature",
"geometry" => { "type" => "MultiLineString", "coordinates" => lines },
"properties" => { "kind" => "track", "name" => child_text(trk, "name") },
}
end
private_class_method :track_feature
def self.coordinate(point_el)
lat = point_el.attributes["lat"].to_f
lon = point_el.attributes["lon"].to_f
ele = child_text(point_el, "ele")
ele ? [lon, lat, ele.to_f] : [lon, lat]
end
private_class_method :coordinate
def self.child_text(el, name)
el.elements[name]&.text&.strip
end
private_class_method :child_text
end
end