require "rexml/document" module Maptool # Converts a GPX file to a GeoJSON FeatureCollection: waypoints () # become Point features, tracks () become MultiLineString features # (one line per ). Routes () 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?(" "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