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.
81 lines
2.6 KiB
Ruby
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
|