source: subversion/utils/ns1togpx/ns1togpx.rb @ 2026

Last change on this file since 2026 was 1456, checked in by ben, 14 years ago

fix for Windows' crap time & date support

  • Property svn:executable set to *
File size: 6.4 KB
Line 
1#!/usr/bin/env ruby
2
3=begin Copyright (C) 2006 Ben Gimpert (ben@somethingmodern.com)
4
5This program is free software; you can redistribute it and/or
6modify it under the terms of the GNU General Public License
7as published by the Free Software Foundation; either version 2
8of the License, or (at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18MA  02111-1307, USA.
19
20=end
21
22module Ns1togpx
23
24        class Converter
25
26                def initialize
27                        @ns1_version = nil
28                end
29
30                def read_uint8(io)
31                        ar = [io.getc]
32                        ar.map { |ch| ch.chr }.join.unpack("C").first
33                end
34
35                def read_uint32(io)
36                        ar = []
37                        4.times { ar << io.getc }
38                        ar.map { |ch| ch.chr }.join.unpack("L").first
39                end
40
41                def read_int32(io)
42                        ar = []
43                        4.times { ar << io.getc }
44                        ar.map { |ch| ch.chr }.join.unpack("l").first
45                end
46
47                def read_double(io)
48                        ar = []
49                        8.times { ar << io.getc }
50                        ar.map { |ch| ch.chr }.join.unpack("E").first  # might need to be an "E" or "D"
51                end
52
53                def read_time_t(io)
54                        read_int32(io)
55                end
56
57                def read_filetime(io)
58                        ar = []
59                        8.times { ar << io.getc }
60                        ar.map { |ch| ch.chr }.join.unpack("Q").first
61                end
62
63                def filetime_to_time(filetime)
64                        nanos = filetime * 100
65                        seconds = nanos / 1000000000
66                        epoch_shift = (1971 - 1601) * 365.25 * 24 * 60 * 60
67                        Time.utc(1971) + (seconds - epoch_shift)
68                end
69
70                def time_t_to_time(time_t)
71                        Time.utc(1970) + time_t
72                end
73
74                def convert(ns1, gpx)
75                        write_header(gpx)
76                        ns1_sig = ns1.read(4)
77                        throw "Does not seem to be an NS1 file" unless ns1_sig == "NetS"
78                        @ns1_version = read_uint32(ns1)
79                        $stderr.puts "NS1 version #{@ns1_version}"
80                        ns1_num_apinfo = read_uint32(ns1)
81                        $stderr.puts "number of APINFO entries = #{ns1_num_apinfo}"
82                        ns1_num_apinfo.times do |i|
83                                if ns1.eof?
84                                        $stderr.puts "WARNING: Premature end of NS1 file, #{ns1_num_apinfo} APINFO entries expected, processed #{i}."
85                                        break
86                                end
87                                convert_apinfo(ns1, gpx)
88                        end
89                        write_footer(gpx)
90                end
91
92                def convert_apinfo(ns1, gpx)
93                        case @ns1_version
94                        when 1
95                                $stderr.puts "No GPS data in version 1 of the NS1 format"
96                        when 6
97                                ns1_ssid_length = read_uint8(ns1)
98                                ns1_ssid = ns1.read(ns1_ssid_length)
99                                ns1.read(38)  # skip ahead to the LastSeen FILETIME
100                                ns1_lastseen = read_filetime(ns1)
101                                ns1_time = filetime_to_time(ns1_lastseen)
102                                ns1_lat = read_double(ns1)
103                                ns1_long = read_double(ns1)
104                                write_point(gpx, ns1_lat, ns1_long, ns1_time)
105                                ns1_num_apdata = read_uint32(ns1)
106                                ns1_num_apdata.times do |i|
107                                        if ns1.eof?
108                                                $stderr.puts "WARNING: Premature end of NS1 file, #{ns1_num_apdata} APDATA entries expected, processed #{i}."
109                                                break
110                                        end
111                                        convert_apdata(ns1, gpx)
112                                end
113                                ns1_name_length = read_uint8(ns1)
114                                ns1.read(ns1_name_length)
115                        when 11
116                                ns1_ssid_length = read_uint8(ns1)
117                                ns1_ssid = ns1.read(ns1_ssid_length)
118                                ns1.read(34)  # skip ahead to the LastSeen FILETIME
119                                ns1_lastseen = read_filetime(ns1)
120                                ns1_time = filetime_to_time(ns1_lastseen)
121                                ns1_lat = read_double(ns1)
122                                ns1_long = read_double(ns1)
123                                write_point(gpx, ns1_lat, ns1_long, ns1_time)
124                                ns1_num_apdata = read_uint32(ns1)
125                                ns1_num_apdata.times do
126                                        if ns1.eof?
127                                                $stderr.puts "WARNING: Premature end of NS1 file, #{ns1_num_apdata} APDATA entries expected, processed #{i}."
128                                                break
129                                        end
130                                        convert_apdata(ns1, gpx)
131                                end
132                                ns1_name_length = read_uint8(ns1)
133                                ns1.read(ns1_name_length)
134                                ns1.read(36)  # skip the rest of the v11 APINFO
135                        when 12
136                                ns1_ssid_length = read_uint8(ns1)
137                                ns1_ssid = ns1.read(ns1_ssid_length)
138                                ns1.read(34)  # skip ahead to the LastSeen FILETIME
139                                ns1_lastseen = read_filetime(ns1)
140                                ns1_time = filetime_to_time(ns1_lastseen)
141                                ns1_lat = read_double(ns1)
142                                ns1_long = read_double(ns1)
143                                write_point(gpx, ns1_lat, ns1_long, ns1_time)
144                                ns1_num_apdata = read_uint32(ns1)
145                                ns1_num_apdata.times do
146                                        if ns1.eof?
147                                                $stderr.puts "WARNING: Premature end of NS1 file, #{ns1_num_apdata} APDATA entries expected, processed #{i}."
148                                                break
149                                        end
150                                        convert_apdata(ns1, gpx)
151                                end
152                                ns1_name_length = read_uint8(ns1)
153                                ns1.read(ns1_name_length)
154                                ns1.read(40)  # skip ahead to the 802.11 information elements
155                                ns1_ie_length = read_uint32(ns1)
156                                ns1.read(ns1_ie_length)
157                        else
158                                throw "Unsupported NS1 version number, #{@ns1_version}."
159                        end
160                end
161
162                def convert_apdata(ns1, gpx)
163                        ns1_time = nil
164                        if (3..4).member?(@ns1_version)
165                                ns1_timefield = read_time_t(ns1)
166                                ns1_time = time_t_to_time(ns1_timefield)
167                        else
168                                ns1_timefield = read_filetime(ns1)
169                                ns1_time = filetime_to_time(ns1_timefield)
170                        end
171                        ns1.read(8)  # skip ahead to the Location Source
172                        ns1_location_source = read_int32(ns1)
173                        if ns1_location_source == 1
174                                convert_gpsdata(ns1, gpx, ns1_time)
175                        end
176                end
177
178                def convert_gpsdata(ns1, gpx, time)
179                        ns1_lat = read_double(ns1)
180                        ns1_long = read_double(ns1)
181                        write_point(gpx, ns1_lat, ns1_long, time)
182                        ns1.read(44)  # skip the rest
183                end
184
185                def time_to_gpxtime(time)
186                        time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
187                end
188
189                def write_header(io)
190                        io.print <<EOF
191<?xml version="1.0"?>
192<gpx version="1.0" creator="ns1togpx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/0" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
193<time>#{time_to_gpxtime(Time.now)}</time>
194<trk>
195<name>ns1togpx Converted GPX Track</name>
196<trkseg>
197EOF
198                end
199
200                def write_point(io, lat, long, time)
201                        io.print <<"EOF"
202<trkpt lat="#{lat}" lon="#{long}">
203<time>#{time_to_gpxtime(time)}</time>
204</trkpt>
205EOF
206                end
207
208                def write_footer(io)
209                        io.print <<EOF
210</trkseg>
211</trk>
212</gpx>
213EOF
214                end
215
216        end
217
218end  # of module
219
220if __FILE__ == $0
221
222        if (ARGV.length >= 1) && (ARGV.first =~ /(-h)|(--help)/i)
223                $stderr.print <<EOF
224ns1togpx Converter
225
226Usage:
227        $ ./ns1togpx <some_file.ns1 >another_file.gpx
228
229EOF
230                exit(1)
231        end
232
233        source = $stdin
234        if (ARGV.length >= 1) && (ARGV.first != "-") && File.exists?(ARGV.first)
235                source = File.open(ARGV.first)
236        end
237        begin
238                source.binmode
239                Ns1togpx::Converter.new.convert(source, $stdout)
240                $stdout.close
241        ensure
242                source.close unless source.closed?
243        end
244
245end
246
Note: See TracBrowser for help on using the repository browser.