open_gpx_2_kml 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|