open_gpx_2_kml 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/.rbenv-version +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +46 -0
- data/README.md +17 -0
- data/Rakefile +2 -0
- data/bin/openGpx2Kml +21 -0
- data/example/config.csv +57 -0
- data/example/windowsconfig.csv +57 -0
- data/harness.rb +16 -0
- data/input/test.gpx +724 -0
- data/lib/tf1_converter/config.rb +63 -0
- data/lib/tf1_converter/csv_file.rb +19 -0
- data/lib/tf1_converter/gpx/track.rb +30 -0
- data/lib/tf1_converter/gpx/trackpoint.rb +22 -0
- data/lib/tf1_converter/gpx/waypoint.rb +76 -0
- data/lib/tf1_converter/gpx_file.rb +19 -0
- data/lib/tf1_converter/kml/track_color.rb +28 -0
- data/lib/tf1_converter/kml/track_node.rb +36 -0
- data/lib/tf1_converter/kml_file.rb +80 -0
- data/lib/tf1_converter/kmz_file.rb +27 -0
- data/lib/tf1_converter/translation.rb +34 -0
- data/lib/tf1_converter/version.rb +3 -0
- data/lib/tf1_converter.rb +3 -0
- data/openGpx2Kml.gemspec +30 -0
- data/output/test.kml +329 -0
- data/spec/fixtures/expected.kml +329 -0
- data/spec/fixtures/ftwood2.gpx +7481 -0
- data/spec/fixtures/ftwood2_expected.kml +1 -0
- data/spec/fixtures/test.gpx +724 -0
- data/spec/fixtures/waypoint-by-name.gpx +709 -0
- data/spec/lib/tf1_converter/gpx/track_spec.rb +20 -0
- data/spec/lib/tf1_converter/gpx/waypoint_spec.rb +70 -0
- data/spec/lib/tf1_converter/kml/track_color_spec.rb +29 -0
- data/spec/lib/tf1_converter/kmz_file_spec.rb +18 -0
- data/spec/lib/tf1_converter/translation_spec.rb +48 -0
- metadata +242 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module TF1Converter
|
4
|
+
class Config
|
5
|
+
|
6
|
+
def self.load(path)
|
7
|
+
last_key = nil
|
8
|
+
current_control = nil
|
9
|
+
@constant_color_switch = false
|
10
|
+
|
11
|
+
CSV.read(path).each do |row|
|
12
|
+
if last_key == 'INPUT'
|
13
|
+
@input = row[0]
|
14
|
+
elsif last_key == 'OUTPUT'
|
15
|
+
@output = row[0]
|
16
|
+
elsif last_key == 'ICON_PATH'
|
17
|
+
@icon_path = row[0]
|
18
|
+
elsif last_key == 'ICONS'
|
19
|
+
@icons = {}
|
20
|
+
current_control = 'ICONS'
|
21
|
+
elsif last_key == 'COLORS'
|
22
|
+
@colors = {}
|
23
|
+
current_control = 'COLORS'
|
24
|
+
elsif last_key == 'USE_CONSTANT_COLOR'
|
25
|
+
@use_constant_color = row[0].strip.downcase == 'true'
|
26
|
+
elsif last_key == 'CONSTANT_COLOR'
|
27
|
+
@constant_color = row[0]
|
28
|
+
end
|
29
|
+
|
30
|
+
if current_control == 'ICONS'
|
31
|
+
if row.empty?
|
32
|
+
current_control = nil
|
33
|
+
else
|
34
|
+
@icons[row[0]] = {
|
35
|
+
'icon' => row[1],
|
36
|
+
'meaning' => row[2],
|
37
|
+
'name' => row[3]
|
38
|
+
}
|
39
|
+
end
|
40
|
+
elsif current_control == 'COLORS'
|
41
|
+
if row.empty?
|
42
|
+
current_control = nil
|
43
|
+
else
|
44
|
+
@colors[row[0]] = row[1]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
last_key = row[0]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
%w(icon_path icons colors input output use_constant_color).each do |name|
|
53
|
+
define_singleton_method(name.to_sym) do
|
54
|
+
instance_variable_get("@#{name}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.constant_color
|
61
|
+
colors[@constant_color]
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module TF1Converter
|
4
|
+
class CsvFile
|
5
|
+
def initialize(waypoints, path)
|
6
|
+
@waypoints = waypoints
|
7
|
+
@path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_csv!
|
11
|
+
CSV.open(@path, 'wb') do |csv|
|
12
|
+
csv << ['filename', 'name', 'meaning', 'time', 'lat', 'long', 'usng', 'elevation']
|
13
|
+
@waypoints.each do |wp|
|
14
|
+
csv << [@path.split('.').first, wp.name, wp.icon_meaning, wp.timestamp, wp.lat, wp.long, wp.usng, wp.elevation]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'trackpoint'
|
2
|
+
|
3
|
+
module TF1Converter
|
4
|
+
module Gpx
|
5
|
+
class Track
|
6
|
+
def initialize(xml_node, color_map = TF1Converter::Config.colors)
|
7
|
+
@node = xml_node
|
8
|
+
@color_map = color_map
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@node.xpath('name').first.text
|
13
|
+
end
|
14
|
+
|
15
|
+
def display_color
|
16
|
+
color_node = @node.xpath('extensions/TrackExtension/DisplayColor').first
|
17
|
+
if color_node
|
18
|
+
@color_map[color_node.text]
|
19
|
+
else
|
20
|
+
'f0000080'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def coordinate_string
|
25
|
+
trackpoints = @node.xpath('trkseg/trkpt').map{ |node| Trackpoint.new(node) }
|
26
|
+
trackpoints.inject([]) { |points, tp| points << tp.to_s }.join(' ')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module TF1Converter
|
2
|
+
module Gpx
|
3
|
+
class Trackpoint
|
4
|
+
def initialize(xml_node)
|
5
|
+
@node = xml_node
|
6
|
+
end
|
7
|
+
|
8
|
+
def lat
|
9
|
+
@node.attribute('lat').value.strip
|
10
|
+
end
|
11
|
+
|
12
|
+
def long
|
13
|
+
@node.attribute('lon').value.strip
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"" << long << ',' << lat << ',0'
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module TF1Converter
|
2
|
+
module Gpx
|
3
|
+
class Waypoint
|
4
|
+
def initialize(xml_node, icon_map = TF1Converter::Config.icons)
|
5
|
+
@node = xml_node
|
6
|
+
@icon_map = icon_map
|
7
|
+
end
|
8
|
+
|
9
|
+
def name
|
10
|
+
name_node = @node.xpath('name').first
|
11
|
+
name_node.nil? ? '' : name_node.text
|
12
|
+
end
|
13
|
+
|
14
|
+
def icon_name
|
15
|
+
if symbol_name
|
16
|
+
map_entry = @icon_map[symbol_name]
|
17
|
+
return map_entry['icon'] if map_entry
|
18
|
+
elsif name
|
19
|
+
@icon_map.values.each do |icon_data|
|
20
|
+
return icon_data['icon'] if icon_data['name'] == name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
'default.png'
|
24
|
+
end
|
25
|
+
|
26
|
+
def icon_meaning
|
27
|
+
if symbol_name
|
28
|
+
map_entry = @icon_map[symbol_name]
|
29
|
+
return map_entry['meaning'] if map_entry
|
30
|
+
end
|
31
|
+
'Default'
|
32
|
+
end
|
33
|
+
|
34
|
+
def timestamp
|
35
|
+
@node.children.select{ |child| child.name == 'cmt' }.first.text
|
36
|
+
end
|
37
|
+
|
38
|
+
def lat
|
39
|
+
@node.attribute('lat').value
|
40
|
+
end
|
41
|
+
|
42
|
+
def long
|
43
|
+
@node.attribute('lon').value
|
44
|
+
end
|
45
|
+
|
46
|
+
def elevation
|
47
|
+
@node.children.select{ |child| child.name == 'ele' }.first.text
|
48
|
+
end
|
49
|
+
|
50
|
+
def usng
|
51
|
+
u = utm_object
|
52
|
+
GeoSwap.utm_to_usng(u.easting, u.northing, u.zone.number, u.zone.letter)
|
53
|
+
end
|
54
|
+
|
55
|
+
def utm
|
56
|
+
utm_object.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def utm_object
|
62
|
+
@_utm_object ||= GeoSwap.lat_long_to_utm(lat.to_f, long.to_f)
|
63
|
+
end
|
64
|
+
|
65
|
+
def symbol_name
|
66
|
+
sym_node = @node.children.select{ |child| child.name == 'sym' }.first
|
67
|
+
if sym_node
|
68
|
+
sym_node.text
|
69
|
+
else
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'gpx/waypoint'
|
2
|
+
require_relative 'gpx/track'
|
3
|
+
|
4
|
+
module TF1Converter
|
5
|
+
class GpxFile
|
6
|
+
def initialize(gpx_file)
|
7
|
+
@gpx = gpx_file
|
8
|
+
end
|
9
|
+
|
10
|
+
def waypoints
|
11
|
+
@gpx.xpath('//gpx/wpt').map{ |node| Gpx::Waypoint.new(node) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def tracks
|
15
|
+
@gpx.xpath('//gpx/trk').map{ |node| Gpx::Track.new(node) }
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module TF1Converter::Kml
|
2
|
+
class TrackColor
|
3
|
+
def self.next
|
4
|
+
if config.use_constant_color
|
5
|
+
config.constant_color
|
6
|
+
else
|
7
|
+
@colors ||= config.colors.values
|
8
|
+
@color_index ||= 0
|
9
|
+
return_color = @colors[@color_index]
|
10
|
+
@color_index += 1
|
11
|
+
if @color_index >= @colors.length
|
12
|
+
@color_index = 0
|
13
|
+
end
|
14
|
+
return_color
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.uncache!
|
19
|
+
@colors = nil
|
20
|
+
@color_index = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def self.config
|
25
|
+
::TF1Converter::Config
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'track_color'
|
2
|
+
|
3
|
+
module TF1Converter
|
4
|
+
module Kml
|
5
|
+
class TrackNode
|
6
|
+
def initialize(track, filename)
|
7
|
+
@track = track
|
8
|
+
@filename = filename
|
9
|
+
end
|
10
|
+
|
11
|
+
def write_to(xml)
|
12
|
+
xml.Style(id: "#{@track.name}_Style") do
|
13
|
+
xml.LineStyle do
|
14
|
+
xml.color TrackColor.next
|
15
|
+
xml.width 3
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
xml.Placemark(id: @track.name) do
|
20
|
+
xml.name @track.name
|
21
|
+
xml.description do
|
22
|
+
xml.cdata @filename
|
23
|
+
end
|
24
|
+
xml.styleUrl "##{@track.name}_Style"
|
25
|
+
xml.LineString do
|
26
|
+
xml.extrude 1
|
27
|
+
xml.tessellate 1
|
28
|
+
xml.altitudeMode 'clampedToGround'
|
29
|
+
xml.coordinates @track.coordinate_string
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative 'kml/track_node'
|
2
|
+
|
3
|
+
module TF1Converter
|
4
|
+
class KmlFile
|
5
|
+
def initialize(waypoints, tracks, filename)
|
6
|
+
@waypoints = waypoints
|
7
|
+
@tracks = tracks
|
8
|
+
@filename = filename
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_xml
|
12
|
+
Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
13
|
+
xml.kml('xmlns' => 'http://www.opengis.net/kml/2.2') do
|
14
|
+
xml.Document do
|
15
|
+
write_xml_header(xml)
|
16
|
+
|
17
|
+
xml.Folder do
|
18
|
+
xml.name "Waypoints"
|
19
|
+
@waypoints.each do |waypoint|
|
20
|
+
write_waypoint_xml(waypoint, xml)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
xml.Folder do
|
25
|
+
xml.name "Tracks"
|
26
|
+
@tracks.each do |track|
|
27
|
+
Kml::TrackNode.new(track, @filename).write_to(xml)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end.to_xml
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def write_xml_header(xml)
|
39
|
+
xml.open 1
|
40
|
+
xml.Snippet(maxLines: '1')
|
41
|
+
xml.description do
|
42
|
+
xml.cdata "#{Time.now.strftime('%m-%d-%Y %I:%M:%S %p')}<br/><br/>TF1 Converter Version 1.0<br/>MO Task Force 1<br/>"
|
43
|
+
end
|
44
|
+
xml.Style(id: "sn_noicon") { xml.IconStyle { xml.Icon } }
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_waypoint_xml(waypoint, xml)
|
48
|
+
xml.Placemark do
|
49
|
+
xml.name(waypoint.name)
|
50
|
+
xml.Snippet(maxLines: '0')
|
51
|
+
xml.Style(id: 'normalPlacemark') do
|
52
|
+
xml.IconStyle do
|
53
|
+
xml.Icon do
|
54
|
+
xml.href("files/#{waypoint.icon_name}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
xml.description do
|
60
|
+
xml.cdata description_for(waypoint)
|
61
|
+
end
|
62
|
+
|
63
|
+
xml.Point do
|
64
|
+
xml.coordinates "#{waypoint.long},#{waypoint.lat}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def description_for(waypoint)
|
70
|
+
desc = ""
|
71
|
+
desc << waypoint.timestamp
|
72
|
+
desc << '<br>' << waypoint.icon_meaning
|
73
|
+
desc << '<br>' << "Filename: #{@filename}"
|
74
|
+
desc << "<br>" << "USNG: #{waypoint.usng}"
|
75
|
+
desc << "<br>" << "Lat,Long: #{waypoint.lat},#{waypoint.long}"
|
76
|
+
desc << "<br>" << "Elevation: #{waypoint.elevation}"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'zip/zip'
|
3
|
+
|
4
|
+
module TF1Converter
|
5
|
+
class KmzFile
|
6
|
+
def self.assemble!(filename)
|
7
|
+
raw_name = filename.split(/[\/\\]/).last
|
8
|
+
zip_path = "#{filename}.zip"
|
9
|
+
FileUtils.rm(zip_path) if File.exists?(zip_path)
|
10
|
+
Zip::ZipFile.open(zip_path, Zip::ZipFile::CREATE) do |zipfile|
|
11
|
+
zipfile.add("#{raw_name}.kml", "#{filename}.kml")
|
12
|
+
zipfile.mkdir("files")
|
13
|
+
Dir.foreach(TF1Converter::Config.icon_path) do |item|
|
14
|
+
if item != '.' && item != '..'
|
15
|
+
zipfile.add("files/#{item}", full_filepath(item))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
FileUtils.mv(zip_path, "#{filename}.kmz")
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.full_filepath(filename)
|
23
|
+
path = "#{TF1Converter::Config.icon_path}/#{filename}"
|
24
|
+
path.gsub('//', '/')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'geo_swap'
|
3
|
+
require_relative 'gpx_file'
|
4
|
+
require_relative 'kml_file'
|
5
|
+
require_relative 'kmz_file'
|
6
|
+
require_relative 'csv_file'
|
7
|
+
|
8
|
+
module TF1Converter
|
9
|
+
class Translation
|
10
|
+
def self.from(file)
|
11
|
+
new(file)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(gpx_file)
|
15
|
+
@filename = File.basename(gpx_file.path).split('.').first
|
16
|
+
parsed_gpx = Nokogiri::XML(gpx_file)
|
17
|
+
parsed_gpx.remove_namespaces!
|
18
|
+
@gpx = GpxFile.new(parsed_gpx)
|
19
|
+
end
|
20
|
+
|
21
|
+
def into(output_file)
|
22
|
+
raw_file_name = output_file.path.split('.').first
|
23
|
+
csv_path = raw_file_name + '.csv'
|
24
|
+
CsvFile.new(@gpx.waypoints, csv_path).to_csv!
|
25
|
+
|
26
|
+
kml = KmlFile.new(@gpx.waypoints, @gpx.tracks, @filename).to_xml
|
27
|
+
output_file.puts kml
|
28
|
+
output_file.close
|
29
|
+
|
30
|
+
kmz = KmzFile.assemble!(raw_file_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
data/openGpx2Kml.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tf1_converter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "open_gpx_2_kml"
|
8
|
+
gem.version = TF1Converter::VERSION
|
9
|
+
gem.authors = ["Ethan Vizitei"]
|
10
|
+
gem.email = ["ethan.vizitei@gmail.com"]
|
11
|
+
gem.description = %q{A GPX to KML converter for FEMA US&R}
|
12
|
+
gem.summary = %q{A GPX to KML converter for FEMA US&R}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rake', '10.0.3'
|
21
|
+
gem.add_development_dependency 'rspec', '2.12.0'
|
22
|
+
gem.add_development_dependency 'pry', '0.9.11.4'
|
23
|
+
gem.add_development_dependency 'timecop', '0.5.9.2'
|
24
|
+
|
25
|
+
gem.add_dependency 'thor', '0.17.0'
|
26
|
+
gem.add_dependency 'nokogiri', '1.5.6'
|
27
|
+
gem.add_dependency 'builder', '3.1.4'
|
28
|
+
gem.add_dependency 'geo_swap', '0.2.1'
|
29
|
+
gem.add_dependency 'rubyzip', '0.9.9'
|
30
|
+
end
|