kamelopard 0.0.3
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/lib/kamelopard/classes.rb +1706 -0
- data/lib/kamelopard/functions.rb +119 -0
- data/lib/kamelopard/geocode.rb +62 -0
- data/lib/kamelopard/pointlist.rb +127 -0
- data/lib/kamelopard.rb +3 -0
- metadata +50 -0
|
@@ -0,0 +1,1706 @@
|
|
|
1
|
+
# vim:ts=4:sw=4:et:smartindent:nowrap
|
|
2
|
+
|
|
3
|
+
# Classes to manage various KML objects. See
|
|
4
|
+
# http://code.google.com/apis/kml/documentation/kmlreference.html for a
|
|
5
|
+
# description of KML
|
|
6
|
+
|
|
7
|
+
require 'singleton'
|
|
8
|
+
require 'kamelopard/pointlist'
|
|
9
|
+
|
|
10
|
+
@@sequence = 0
|
|
11
|
+
|
|
12
|
+
def get_next_id # :nodoc
|
|
13
|
+
@@sequence += 1
|
|
14
|
+
@@sequence
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#--
|
|
18
|
+
# Print out a set of kml fields. Expects an array argument. Each entry in the
|
|
19
|
+
# array is itself an array, containing two strings and a boolean. If the first
|
|
20
|
+
# string is nil, the function won't print anything for that element. If it's
|
|
21
|
+
# not null, it consults the boolean. True values tell the function to treat the
|
|
22
|
+
# second string as a KML element name, and print it along with XML decorators
|
|
23
|
+
# and the field value. False values mean just print the second string, with no
|
|
24
|
+
# decorators and no other values
|
|
25
|
+
#++
|
|
26
|
+
def kml_array(m, indent = 0) # :nodoc
|
|
27
|
+
k = ''
|
|
28
|
+
m.map do |a|
|
|
29
|
+
r = ''
|
|
30
|
+
if ! a[0].nil? then
|
|
31
|
+
if a[2] then
|
|
32
|
+
r << "#{ ' ' * indent}<" << a[1] << '>' << a[0].to_s << '</' << a[1] << ">\n"
|
|
33
|
+
else
|
|
34
|
+
r << a[1] << "\n"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
k << r
|
|
38
|
+
end
|
|
39
|
+
k
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#--
|
|
43
|
+
# Accepts XdX'X.X", XDXmX.XXs, XdXmX.XXs, or X.XXXX with either +/- or N/E/S/W
|
|
44
|
+
#++
|
|
45
|
+
def convert_coord(a) # :nodoc
|
|
46
|
+
a = a.to_s.upcase.strip
|
|
47
|
+
|
|
48
|
+
mult = 1
|
|
49
|
+
if a =~ /^-/ then
|
|
50
|
+
mult *= -1
|
|
51
|
+
end
|
|
52
|
+
a = a.sub /^\+|-/, ''
|
|
53
|
+
a = a.strip
|
|
54
|
+
|
|
55
|
+
if a =~ /[SW]$/ then
|
|
56
|
+
mult *= -1
|
|
57
|
+
end
|
|
58
|
+
a = a.sub /[NESW]$/, ''
|
|
59
|
+
a = a.strip
|
|
60
|
+
|
|
61
|
+
if a =~ /^\d+(\.\d+)?$/ then
|
|
62
|
+
# coord needs no transformation
|
|
63
|
+
1
|
|
64
|
+
elsif a =~ /^\d+D\d+M\d+(\.\d+)?S$/ then
|
|
65
|
+
# coord is in dms
|
|
66
|
+
p = a.split /[D"']/
|
|
67
|
+
a = p[0].to_f + (p[2].to_f / 60.0 + p[1].to_f) / 60.0
|
|
68
|
+
elsif a =~ /^\d+D\d+'\d+(\.\d+)?"$/ then
|
|
69
|
+
# coord is in d'"
|
|
70
|
+
p = a.split /[D"']/
|
|
71
|
+
a = p[0].to_f + (p[2].to_f / 60.0 + p[1].to_f) / 60.0
|
|
72
|
+
else
|
|
73
|
+
raise "Couldn't determine coordinate format for #{a}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# check that it's within range
|
|
77
|
+
a = a.to_f * mult
|
|
78
|
+
raise "Coordinate #{a} out of range" if a > 180 or a < -180
|
|
79
|
+
return a
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Base class for all Kamelopard objects. Manages object ID and a single
|
|
83
|
+
# comment string associated with the object
|
|
84
|
+
class KMLObject
|
|
85
|
+
attr_accessor :id, :comment
|
|
86
|
+
|
|
87
|
+
def initialize(comment = nil)
|
|
88
|
+
@id = "#{self.class.name}_#{ get_next_id }"
|
|
89
|
+
@comment = comment.gsub(/</, '<') unless comment.nil?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns KML string for this object. Objects should override this method
|
|
93
|
+
def to_kml(indent = 0)
|
|
94
|
+
if @comment.nil? or @comment == '' then
|
|
95
|
+
''
|
|
96
|
+
else
|
|
97
|
+
"#{ ' ' * indent }<!-- #{ @comment } -->\n"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Abstract base class for KMLPoint and several other classes
|
|
103
|
+
class Geometry < KMLObject
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Represents a Point in KML.
|
|
107
|
+
class KMLPoint < Geometry
|
|
108
|
+
attr_accessor :longitude, :latitude, :altitude, :altitudeMode, :extrude
|
|
109
|
+
def initialize(long, lat, alt=0, altmode=:clampToGround, extrude=false)
|
|
110
|
+
super()
|
|
111
|
+
@longitude = convert_coord(long)
|
|
112
|
+
@latitude = convert_coord(lat)
|
|
113
|
+
@altitude = alt
|
|
114
|
+
@altitudeMode = altmode
|
|
115
|
+
@extrude = extrude
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def to_s
|
|
119
|
+
"KMLPoint (#{@longitude}, #{@latitude}, #{@altitude}, mode = #{@altitudeMode}, #{ @extrude ? 'extruded' : 'not extruded' })"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def to_kml(indent = 0, short = false)
|
|
123
|
+
# The short form includes only the coordinates tag
|
|
124
|
+
k = super(indent + 4) + "#{ ' ' * indent }<Point id=\"#{ @id }\">\n"
|
|
125
|
+
k << "#{ ' ' * indent } <extrude>#{ @extrude ? 1 : 0 }</extrude>\n" unless short
|
|
126
|
+
if not short then
|
|
127
|
+
if @altitudeMode == :clampToGround or @altitudeMode == :relativeToGround or @altitudeMode == :absolute then
|
|
128
|
+
k << "#{ ' ' * indent } <altitudeMode>#{ @altitudeMode }</altitudeMode>\n"
|
|
129
|
+
else
|
|
130
|
+
k << "#{ ' ' * indent } <gx:altitudeMode>#{ @altitudeMode }</gx:altitudeMode>\n"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
k << "#{ ' ' * indent } <coordinates>#{ @longitude }, #{ @latitude }, #{ @altitude }</coordinates>\n"
|
|
134
|
+
k << "#{ ' ' * indent }</Point>\n"
|
|
135
|
+
k
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Helper class for KML objects which need to know about several points at once
|
|
140
|
+
class CoordinateList
|
|
141
|
+
attr_reader :coordinates
|
|
142
|
+
|
|
143
|
+
# Accepts an optional array of coordinates in any format add_element
|
|
144
|
+
# accepts
|
|
145
|
+
def initialize(coords = nil)
|
|
146
|
+
# Internally we store coordinates as an array of three-element
|
|
147
|
+
# arrays
|
|
148
|
+
@coordinates = []
|
|
149
|
+
if not coords.nil? then
|
|
150
|
+
add_element coords
|
|
151
|
+
else
|
|
152
|
+
@coordinates = []
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def to_kml(indent = 0)
|
|
157
|
+
k = "#{ ' ' * indent }<coordinates>\n#{ ' ' * indent } "
|
|
158
|
+
if not @coordinates.nil? then
|
|
159
|
+
@coordinates.each do |a|
|
|
160
|
+
k << "#{ a[0] },#{ a[1] }"
|
|
161
|
+
k << ",#{ a[2] }" if a.size > 2
|
|
162
|
+
k << ' '
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
k << "\n#{ ' ' * indent}</coordinates>\n"
|
|
166
|
+
k
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Alias for add_element
|
|
170
|
+
def <<(a)
|
|
171
|
+
add_element a
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Adds one or more elements to this CoordinateList. The argument can be in any of several formats:
|
|
175
|
+
# * An array of arrays of numeric objects, in the form [ longitude,
|
|
176
|
+
# latitude, altitude (optional) ]
|
|
177
|
+
# * A KMLPoint, or some other object that response to latitude, longitude, and altitude methods
|
|
178
|
+
# * An array of the above
|
|
179
|
+
# * Another CoordinateList, to append to this on
|
|
180
|
+
# Note that this will not accept a one-dimensional array of numbers to add
|
|
181
|
+
# a single point. Instead, create a KMLPoint with those numbers, and pass
|
|
182
|
+
# it to add_element
|
|
183
|
+
def add_element(a)
|
|
184
|
+
if a.kind_of? Enumerable then
|
|
185
|
+
# We've got some sort of array or list. It could be a list of
|
|
186
|
+
# floats, to become one coordinate, or it could be several
|
|
187
|
+
# coordinates
|
|
188
|
+
t = a.to_a.first
|
|
189
|
+
if t.kind_of? Enumerable then
|
|
190
|
+
# At this point we assume we've got an array of float-like
|
|
191
|
+
# objects. The second-level arrays need to have two or three
|
|
192
|
+
# entries -- long, lat, and (optionally) alt
|
|
193
|
+
a.each do |i|
|
|
194
|
+
if i.size < 2 then
|
|
195
|
+
raise "There aren't enough objects here to make a 2- or 3-element coordinate"
|
|
196
|
+
elsif i.size >= 3 then
|
|
197
|
+
@coordinates << [ i[0].to_f, i[1].to_f, i[2].to_f ]
|
|
198
|
+
else
|
|
199
|
+
@coordinates << [ i[0].to_f, i[1].to_f ]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
elsif t.respond_to? 'longitude' and
|
|
203
|
+
t.respond_to? 'latitude' and
|
|
204
|
+
t.respond_to? 'altitude' then
|
|
205
|
+
# This object can cough up a set of coordinates
|
|
206
|
+
a.each do |i|
|
|
207
|
+
@coordinates << [i.longitude, i.latitude, i.altitude]
|
|
208
|
+
end
|
|
209
|
+
else
|
|
210
|
+
# I dunno what it is
|
|
211
|
+
raise "Kamelopard can't understand this object as a coordinate"
|
|
212
|
+
end
|
|
213
|
+
elsif a.kind_of? CoordinateList then
|
|
214
|
+
# Append this coordinate list
|
|
215
|
+
@coordinates << a.coordinates
|
|
216
|
+
else
|
|
217
|
+
# This is one element. It better know how to make latitude, longitude, etc.
|
|
218
|
+
if a.respond_to? 'longitude' and
|
|
219
|
+
a.respond_to? 'latitude' and
|
|
220
|
+
a.respond_to? 'altitude' then
|
|
221
|
+
@coordinates << [a.longitude, a.latitude, a.altitude]
|
|
222
|
+
else
|
|
223
|
+
raise "Kamelopard can't understand this object as a coordinate"
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Corresponds to the KML LineString object
|
|
230
|
+
class LineString < Geometry
|
|
231
|
+
attr_accessor :altitudeOffset, :extrude, :tessellate, :altitudeMode, :drawOrder, :longitude, :latitude, :altitude
|
|
232
|
+
attr_reader :coordinates
|
|
233
|
+
|
|
234
|
+
def initialize(coords, altMode = :clampToGround)
|
|
235
|
+
super()
|
|
236
|
+
@altitudeMode = altMode
|
|
237
|
+
set_coords coords
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Sets @coordinates element
|
|
241
|
+
def set_coords(a)
|
|
242
|
+
if a.kind_of? CoordinateList then
|
|
243
|
+
@coordinates = a
|
|
244
|
+
else
|
|
245
|
+
@coordinates = CoordinateList.new(a)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Appends an element to this LineString's CoordinateList. See CoordinateList#add_element
|
|
250
|
+
def <<(a)
|
|
251
|
+
@coordinates << a
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def to_kml(indent = 0)
|
|
255
|
+
|
|
256
|
+
k = super(indent + 4) + "#{ ' ' * indent }<LineString id =\"#{ @id }\">\n"
|
|
257
|
+
k << kml_array([
|
|
258
|
+
[@altitudeOffset, 'gx:altitudeOffset', true],
|
|
259
|
+
[@extrude, 'extrude', true],
|
|
260
|
+
[@tessellate, 'tessellate', true],
|
|
261
|
+
[@drawOrder, 'gx:drawOrder', true]
|
|
262
|
+
], indent + 4)
|
|
263
|
+
k << @coordinates.to_kml(indent + 4) unless @coordinates.nil?
|
|
264
|
+
if @altitudeMode == :clampToGround or @altitudeMode == :relativeToGround or @altitudeMode == :absolute then
|
|
265
|
+
k << "#{ ' ' * indent } <altitudeMode>#{ @altitudeMode }</altitudeMode>\n"
|
|
266
|
+
else
|
|
267
|
+
k << "#{ ' ' * indent } <gx:altitudeMode>#{ @altitudeMode }</gx:altitudeMode>\n"
|
|
268
|
+
end
|
|
269
|
+
k << "#{ ' ' * indent }</LineString>\n"
|
|
270
|
+
k
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Corresponds to KML's LinearRing object
|
|
275
|
+
class LinearRing < Geometry
|
|
276
|
+
attr_accessor :altitudeOffset, :extrude, :tessellate, :altitudeMode, :coordinates
|
|
277
|
+
|
|
278
|
+
def initialize(coordinates = nil, tessellate = 0, extrude = 0, altitudeMode = :clampToGround, altitudeOffset = nil)
|
|
279
|
+
super()
|
|
280
|
+
if coordinates.nil? then
|
|
281
|
+
@coordinates = nil
|
|
282
|
+
elsif coordinates.kind_of? CoordinateList then
|
|
283
|
+
@coordinates = coordinates
|
|
284
|
+
else
|
|
285
|
+
@coordinates = CoordinateList.new(coordinates)
|
|
286
|
+
end
|
|
287
|
+
@tessellate = tessellate
|
|
288
|
+
@extrude = extrude
|
|
289
|
+
@altitudeMode = altitudeMode
|
|
290
|
+
@altitudeOffset = altitudeOffset
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Sets @coordinates element
|
|
294
|
+
def set_coords(a)
|
|
295
|
+
if a.kind_of? CoordinateList then
|
|
296
|
+
@coordinates = a
|
|
297
|
+
else
|
|
298
|
+
@coordinates = CoordinateList.new(a)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Appends an element to this LinearRing's CoordinateList. See CoordinateList#add_element
|
|
303
|
+
def <<(a)
|
|
304
|
+
@coordinates << a
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def to_kml(indent = 0)
|
|
308
|
+
k = super(indent + 4) + "#{ ' ' * indent }<LinearRing id=\"#{ @id }\">\n"
|
|
309
|
+
k << "#{ ' ' * indent } <gx:altitudeOffset>#{ @altitudeOffset }</gx:altitudeOffset>\n" unless @altitudeOffset.nil?
|
|
310
|
+
k << "#{ ' ' * indent } <tessellate>#{ @tessellate }</tessellate>\n" unless @tessellate.nil?
|
|
311
|
+
k << "#{ ' ' * indent } <extrude>#{ @extrude }</extrude>\n" unless @extrude.nil?
|
|
312
|
+
if not @altitudeMode.nil? then
|
|
313
|
+
if @altitudeMode == :clampToGround or @altitudeMode == :relativeToGround or @altitudeMode == :absolute then
|
|
314
|
+
k << "#{ ' ' * indent } <altitudeMode>#{ @altitudeMode }</altitudeMode>\n"
|
|
315
|
+
else
|
|
316
|
+
k << "#{ ' ' * indent } <gx:altitudeMode>#{ @altitudeMode }</gx:altitudeMode>\n"
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
k << @coordinates.to_kml(indent + 4)
|
|
320
|
+
k << "#{ ' ' * indent }</LinearRing>\n"
|
|
321
|
+
k
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Abstract class corresponding to KML's AbstractView object
|
|
326
|
+
class AbstractView < KMLObject
|
|
327
|
+
attr_accessor :timestamp, :timespan, :options, :point, :heading, :tilt, :roll, :range, :altitudeMode
|
|
328
|
+
def initialize(className, point = nil, heading = 0, tilt = 0, roll = 0, range = 0, altitudeMode = :clampToGround)
|
|
329
|
+
raise "className argument must not be nil" if className.nil?
|
|
330
|
+
super()
|
|
331
|
+
@point = point
|
|
332
|
+
@options = {}
|
|
333
|
+
@className = className
|
|
334
|
+
if point.nil? then
|
|
335
|
+
@point = nil
|
|
336
|
+
elsif point.kind_of? Placemark then
|
|
337
|
+
@point = point.point
|
|
338
|
+
else
|
|
339
|
+
@point = point
|
|
340
|
+
end
|
|
341
|
+
@heading = heading
|
|
342
|
+
@tilt = tilt
|
|
343
|
+
@roll = roll
|
|
344
|
+
@range = range
|
|
345
|
+
@altitudeMode = altitudeMode
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def point=(point)
|
|
349
|
+
@point.longitude = point.longitude
|
|
350
|
+
@point.latitude = point.latitude
|
|
351
|
+
@point.altitude = point.altitude
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def longitude
|
|
355
|
+
@point.nil? ? nil : @point.longitude
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def latitude
|
|
359
|
+
@point.nil? ? nil : @point.latitude
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def altitude
|
|
363
|
+
@point.nil? ? nil : @point.altitude
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def longitude=(a)
|
|
367
|
+
if @point.nil? then
|
|
368
|
+
@point = KMLPoint.new(a, 0)
|
|
369
|
+
else
|
|
370
|
+
@point.longitude = a
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def latitude=(a)
|
|
375
|
+
if @point.nil? then
|
|
376
|
+
@point = KMLPoint.new(0, a)
|
|
377
|
+
else
|
|
378
|
+
@point.latitude = a
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def altitude=(a)
|
|
383
|
+
if @point.nil? then
|
|
384
|
+
@point = KMLPoint.new(0, 0, a)
|
|
385
|
+
else
|
|
386
|
+
@point.altitude = a
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def to_kml(indent = 0)
|
|
391
|
+
t = "#{ ' ' * indent }<#{ @className } id=\"#{ @id }\">\n"
|
|
392
|
+
t << super(indent)
|
|
393
|
+
t << kml_array([
|
|
394
|
+
[ @point.nil? ? nil : @point.longitude, 'longitude', true ],
|
|
395
|
+
[ @point.nil? ? nil : @point.latitude, 'latitude', true ],
|
|
396
|
+
[ @point.nil? ? nil : @point.altitude, 'altitude', true ],
|
|
397
|
+
[ @heading, 'heading', true ],
|
|
398
|
+
[ @tilt, 'tilt', true ],
|
|
399
|
+
[ @range, 'range', true ],
|
|
400
|
+
[ @roll, 'roll', true ]
|
|
401
|
+
], indent + 4)
|
|
402
|
+
if @altitudeMode == :clampToGround or @altitudeMode == :relativeToGround or @altitudeMode == :absolute then
|
|
403
|
+
t << "#{ ' ' * indent } <altitudeMode>#{ @altitudeMode }</altitudeMode>\n"
|
|
404
|
+
else
|
|
405
|
+
t << "#{ ' ' * indent } <gx:altitudeMode>#{ @altitudeMode }</gx:altitudeMode>\n"
|
|
406
|
+
end
|
|
407
|
+
if @options.keys.length > 0 then
|
|
408
|
+
t << "#{ ' ' * indent } <gx:ViewerOptions>\n"
|
|
409
|
+
@options.each do |k, v|
|
|
410
|
+
t << "#{ ' ' * ( indent + 8 ) }<gx:option name=\"#{ k }\" enabled=\"#{ v ? 'true' : 'false' }\" />\n"
|
|
411
|
+
end
|
|
412
|
+
t << "#{ ' ' * indent } </gx:ViewerOptions>\n"
|
|
413
|
+
end
|
|
414
|
+
if not @timestamp.nil? then
|
|
415
|
+
t << @timestamp.to_kml(indent+4, 'gx')
|
|
416
|
+
elsif not @timespan.nil? then
|
|
417
|
+
t << @timespan.to_kml(indent+4, 'gx')
|
|
418
|
+
end
|
|
419
|
+
t << "#{ ' ' * indent }</#{ @className }>\n"
|
|
420
|
+
t
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def [](a)
|
|
424
|
+
return @options[a]
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def []=(a, b)
|
|
428
|
+
if not b.kind_of? FalseClass and not b.kind_of? TrueClass then
|
|
429
|
+
raise 'Option value must be boolean'
|
|
430
|
+
end
|
|
431
|
+
if a != :streetview and a != :historicalimagery and a != :sunlight then
|
|
432
|
+
raise 'Option index must be :streetview, :historicalimagery, or :sunlight'
|
|
433
|
+
end
|
|
434
|
+
@options[a] = b
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# Corresponds to KML's Camera object
|
|
439
|
+
class Camera < AbstractView
|
|
440
|
+
def initialize(point = nil, heading = 0, tilt = 0, roll = 0, altitudeMode = :clampToGround)
|
|
441
|
+
super('Camera', point, heading, tilt, roll, nil, altitudeMode)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def range
|
|
445
|
+
raise "The range element is part of LookAt objects, not Camera objects"
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def range=
|
|
449
|
+
# The range element doesn't exist in Camera objects
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Corresponds to KML's LookAt object
|
|
454
|
+
class LookAt < AbstractView
|
|
455
|
+
def initialize(point = nil, heading = 0, tilt = 0, range = 0, altitudeMode = :clampToGround)
|
|
456
|
+
super('LookAt', point, heading, tilt, nil, range, altitudeMode)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def roll
|
|
460
|
+
raise "The roll element is part of Camera objects, not LookAt objects"
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def roll=
|
|
464
|
+
# The roll element doesn't exist in LookAt objects
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Abstract class corresponding to KML's TimePrimitive object
|
|
469
|
+
class TimePrimitive < KMLObject
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# Corresponds to KML's TimeStamp object. The @when attribute must be in a format KML understands.
|
|
473
|
+
class TimeStamp < TimePrimitive
|
|
474
|
+
attr_accessor :when
|
|
475
|
+
def initialize(t_when)
|
|
476
|
+
super()
|
|
477
|
+
@when = t_when
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def to_kml(indent = 0, ns = nil)
|
|
481
|
+
prefix = ''
|
|
482
|
+
prefix = ns + ':' unless ns.nil?
|
|
483
|
+
|
|
484
|
+
k = super(indent + 4)
|
|
485
|
+
k << <<-timestamp
|
|
486
|
+
#{ ' ' * indent }<#{ prefix }TimeStamp id="#{ @id }">
|
|
487
|
+
#{ ' ' * indent } <when>#{ @when }</when>
|
|
488
|
+
#{ ' ' * indent }</#{ prefix }TimeStamp>
|
|
489
|
+
timestamp
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# Corresponds to KML's TimeSpan object. @begin and @end must be in a format KML
|
|
494
|
+
# understands.
|
|
495
|
+
class TimeSpan < TimePrimitive
|
|
496
|
+
attr_accessor :begin, :end
|
|
497
|
+
def initialize(t_begin, t_end)
|
|
498
|
+
super()
|
|
499
|
+
@begin = t_begin
|
|
500
|
+
@end = t_end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def to_kml(indent = 0, ns = nil)
|
|
504
|
+
prefix = ''
|
|
505
|
+
prefix = ns + ':' unless ns.nil?
|
|
506
|
+
|
|
507
|
+
k = super(indent + 4) + "#{ ' ' * indent }<#{ prefix }TimeSpan id=\"#{ @id }\">\n"
|
|
508
|
+
k << "#{ ' ' * indent } <begin>#{ @begin }</begin>\n" unless @begin.nil?
|
|
509
|
+
k << "#{ ' ' * indent } <end>#{ @end }</end>\n" unless @end.nil?
|
|
510
|
+
k << "#{ ' ' * indent }</#{ prefix }TimeSpan>\n"
|
|
511
|
+
k
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Support class for Feature object
|
|
516
|
+
class Snippet
|
|
517
|
+
attr_accessor :text, :maxLines
|
|
518
|
+
def initialize(text = nil, maxLines = 2)
|
|
519
|
+
@text = text
|
|
520
|
+
@maxLines = maxLines
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
def to_kml(indent = 0)
|
|
524
|
+
k = "#{ ' ' * indent }<Snippet maxLines=\"#{maxLines}\">"
|
|
525
|
+
k << text
|
|
526
|
+
k << "#{ ' ' * indent }</Snippet>\n"
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# Abstract class corresponding to KML's Feature object.
|
|
531
|
+
class Feature < KMLObject
|
|
532
|
+
# Abstract class
|
|
533
|
+
attr_accessor :visibility, :open, :atom_author, :atom_link, :name,
|
|
534
|
+
:phoneNumber, :snippet, :description, :abstractView,
|
|
535
|
+
:timeprimitive, :styleUrl, :styleSelector, :region, :metadata,
|
|
536
|
+
:extendedData, :styles
|
|
537
|
+
attr_reader :addressDetails
|
|
538
|
+
|
|
539
|
+
def initialize (name = nil)
|
|
540
|
+
super()
|
|
541
|
+
@name = name
|
|
542
|
+
@visibility = true
|
|
543
|
+
@open = false
|
|
544
|
+
@styles = []
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def timestamp
|
|
548
|
+
@timeprimitive
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def timespan
|
|
552
|
+
@timeprimitive
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
def timestamp=(t)
|
|
556
|
+
@timeprimitive = t
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def timespan=(t)
|
|
560
|
+
@timeprimitive = t
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def addressDetails=(a)
|
|
564
|
+
if a.nil? or a == '' then
|
|
565
|
+
Document.instance.uses_xal = false
|
|
566
|
+
else
|
|
567
|
+
Document.instance.uses_xal = true
|
|
568
|
+
end
|
|
569
|
+
@addressDetails = a
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
# This function accepts either a StyleSelector object, or a string
|
|
573
|
+
# containing the desired StyleSelector's @id
|
|
574
|
+
def styleUrl=(a)
|
|
575
|
+
if a.is_a? String then
|
|
576
|
+
@styleUrl = a
|
|
577
|
+
elsif a.respond_to? 'id' then
|
|
578
|
+
@styleUrl = "##{ a.id }"
|
|
579
|
+
else
|
|
580
|
+
@styleUrl = a.to_s
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def to_kml(indent = 0)
|
|
585
|
+
k = ''
|
|
586
|
+
if self.class == Feature then k << "#{ ' ' * indent }<Feature id=\"#{ @id }\">\n" end
|
|
587
|
+
k << super
|
|
588
|
+
k << kml_array([
|
|
589
|
+
[@name, 'name', true],
|
|
590
|
+
[(@visibility.nil? || @visibility) ? 1 : 0, 'visibility', true],
|
|
591
|
+
[(! @open.nil? && @open) ? 1 : 0, 'open', true],
|
|
592
|
+
[@atom_author, "<atom:author><atom:name>#{ @atom_author }</atom:name></atom:author>", false],
|
|
593
|
+
[@atom_link, 'atom:link', true],
|
|
594
|
+
[@address, 'address', true],
|
|
595
|
+
[@addressDetails, 'xal:AddressDetails', true],
|
|
596
|
+
[@phoneNumber, 'phoneNumber', true],
|
|
597
|
+
[@description, 'description', true],
|
|
598
|
+
[@styleUrl, 'styleUrl', true],
|
|
599
|
+
[@styleSelector, "<styleSelector>#{@styleSelector.nil? ? '' : @styleSelector.to_kml}</styleSelector>", false ],
|
|
600
|
+
[@metadata, 'Metadata', true ],
|
|
601
|
+
[@extendedData, 'ExtendedData', true ]
|
|
602
|
+
], (indent))
|
|
603
|
+
k << styles_to_kml(indent)
|
|
604
|
+
k << @snippet.to_kml(indent) unless @snippet.nil?
|
|
605
|
+
k << @abstractView.to_kml(indent) unless @abstractView.nil?
|
|
606
|
+
k << @timeprimitive.to_kml(indent) unless @timeprimitive.nil?
|
|
607
|
+
k << @region.to_kml(indent) unless @region.nil?
|
|
608
|
+
k << yield if block_given?
|
|
609
|
+
if self.class == Feature then k << "#{ ' ' * indent }</Feature>\n" end
|
|
610
|
+
k
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def styles_to_kml(indent = 0)
|
|
614
|
+
k = ''
|
|
615
|
+
@styles.each do |a|
|
|
616
|
+
k << a.to_kml(indent)
|
|
617
|
+
end
|
|
618
|
+
k
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# Abstract class corresponding to KML's Container object.
|
|
623
|
+
class Container < Feature
|
|
624
|
+
def initialize
|
|
625
|
+
super
|
|
626
|
+
@features = []
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
# Adds a new object to this container.
|
|
630
|
+
def <<(a)
|
|
631
|
+
@features << a
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# Corresponds to KML's Folder object.
|
|
636
|
+
class Folder < Container
|
|
637
|
+
attr_accessor :styles, :folders, :parent_folder
|
|
638
|
+
|
|
639
|
+
def initialize(name = nil)
|
|
640
|
+
super()
|
|
641
|
+
@name = name
|
|
642
|
+
@styles = []
|
|
643
|
+
@folders = []
|
|
644
|
+
Document.instance.folders << self
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
def to_kml(indent = 0)
|
|
648
|
+
h = "#{ ' ' * indent }<Folder id=\"#{@id}\">\n"
|
|
649
|
+
h << super(indent + 4)
|
|
650
|
+
@features.each do |a|
|
|
651
|
+
h << a.to_kml(indent + 4)
|
|
652
|
+
end
|
|
653
|
+
@folders.each do |a|
|
|
654
|
+
h << a.to_kml(indent + 4)
|
|
655
|
+
end
|
|
656
|
+
h << "#{ ' ' * indent }</Folder>\n";
|
|
657
|
+
h
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
# Folders can have parent folders; returns true if this folder has one
|
|
661
|
+
def has_parent?
|
|
662
|
+
not @parent_folder.nil?
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
# Folders can have parent folders; sets this folder's parent
|
|
666
|
+
def parent_folder=(a)
|
|
667
|
+
@parent_folder = a
|
|
668
|
+
a.folders << self
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def get_stack_trace # :nodoc
|
|
673
|
+
k = ''
|
|
674
|
+
caller.each do |a| k << "#{a}\n" end
|
|
675
|
+
k
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
# Represents KML's Document class. This is a Singleton object; Kamelopard
|
|
679
|
+
# scripts can (for now) manage only one Document at a time.
|
|
680
|
+
class Document < Container
|
|
681
|
+
include Singleton
|
|
682
|
+
attr_accessor :flyto_mode, :folders, :tours, :uses_xal
|
|
683
|
+
|
|
684
|
+
def initialize
|
|
685
|
+
@tours = []
|
|
686
|
+
@folders = []
|
|
687
|
+
@styles = []
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
# Returns the current Tour object
|
|
691
|
+
def tour
|
|
692
|
+
@tours << Tour.new if @tours.length == 0
|
|
693
|
+
@tours.last
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
# Returns the current Folder object
|
|
697
|
+
def folder
|
|
698
|
+
if @folders.size == 0 then
|
|
699
|
+
Folder.new
|
|
700
|
+
end
|
|
701
|
+
@folders.last
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def styles_to_kml(indent)
|
|
705
|
+
''
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def to_kml
|
|
709
|
+
xal = ''
|
|
710
|
+
if @uses_xal then
|
|
711
|
+
xal = ' xmlns:xal="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"'
|
|
712
|
+
end
|
|
713
|
+
h = <<-doc_header
|
|
714
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
715
|
+
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"#{ xal }>
|
|
716
|
+
<Document>
|
|
717
|
+
doc_header
|
|
718
|
+
|
|
719
|
+
h << super(4)
|
|
720
|
+
|
|
721
|
+
# Print styles first
|
|
722
|
+
@styles.map do |a| h << a.to_kml(4) unless a.attached? end
|
|
723
|
+
|
|
724
|
+
# then folders
|
|
725
|
+
@folders.map do |a| h << a.to_kml(4) unless a.has_parent? end
|
|
726
|
+
|
|
727
|
+
# then tours
|
|
728
|
+
@tours.map do |a| h << a.to_kml(4) end
|
|
729
|
+
h << "</Document>\n</kml>\n"
|
|
730
|
+
|
|
731
|
+
h
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# Corresponds to KML's ColorStyle object. Color is stored as an 8-character hex
|
|
736
|
+
# string, with two characters each of alpha, blue, green, and red values, in
|
|
737
|
+
# that order, matching the ordering the KML spec demands.
|
|
738
|
+
class ColorStyle < KMLObject
|
|
739
|
+
attr_accessor :color
|
|
740
|
+
attr_reader :colormode
|
|
741
|
+
|
|
742
|
+
def initialize(color, colormode = :normal)
|
|
743
|
+
super()
|
|
744
|
+
# Note: color element order is aabbggrr
|
|
745
|
+
@color = color
|
|
746
|
+
validate_colormode colormode
|
|
747
|
+
@colormode = colormode # Can be :normal or :random
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def validate_colormode(a)
|
|
751
|
+
raise "colorMode must be either \"normal\" or \"random\"" unless a == :normal or a == :random
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
def colormode=(a)
|
|
755
|
+
validate_colormode a
|
|
756
|
+
@colormode = a
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
def alpha
|
|
760
|
+
@color[0,1]
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
def alpha=(a)
|
|
764
|
+
@color[0,1] = a
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
def blue
|
|
768
|
+
@color[2,1]
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
def blue=(a)
|
|
772
|
+
@color[2,1] = a
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def green
|
|
776
|
+
@color[4,1]
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
def green=(a)
|
|
780
|
+
@color[4,1] = a
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
def red
|
|
784
|
+
@color[6,1]
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
def red=(a)
|
|
788
|
+
@color[6,1] = a
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
def to_kml(indent = 0)
|
|
792
|
+
|
|
793
|
+
super + <<-colorstyle
|
|
794
|
+
#{ ' ' * indent }<color>#{@color}</color>
|
|
795
|
+
#{ ' ' * indent }<colorMode>#{@colormode}</colorMode>
|
|
796
|
+
colorstyle
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
# Corresponds to KML's BalloonStyle object. Color is stored as an 8-character hex
|
|
801
|
+
# string, with two characters each of alpha, blue, green, and red values, in
|
|
802
|
+
# that order, matching the ordering the KML spec demands.
|
|
803
|
+
class BalloonStyle < ColorStyle
|
|
804
|
+
attr_accessor :bgcolor, :text, :textcolor, :displaymode
|
|
805
|
+
|
|
806
|
+
# Note: color element order is aabbggrr
|
|
807
|
+
def initialize(text = '', textcolor = 'ff000000', bgcolor = 'ffffffff', displaymode = :default)
|
|
808
|
+
super(nil, :normal)
|
|
809
|
+
@bgcolor = bgcolor
|
|
810
|
+
@text = text
|
|
811
|
+
@textcolor = textcolor
|
|
812
|
+
@displaymode = displaymode
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
def to_kml(indent = 0)
|
|
816
|
+
super + <<-balloonstyle
|
|
817
|
+
#{ ' ' * indent }<BalloonStyle id="#{@id}">
|
|
818
|
+
#{ ' ' * indent } <bgColor>#{@bgcolor}</bgColor>
|
|
819
|
+
#{ ' ' * indent } <text>#{@text}</text>
|
|
820
|
+
#{ ' ' * indent } <textColor>#{@textcolor}</textColor>
|
|
821
|
+
#{ ' ' * indent } <displayMode>#{@displaymode}</displayMode>
|
|
822
|
+
#{ ' ' * indent }</BalloonStyle>
|
|
823
|
+
balloonstyle
|
|
824
|
+
end
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
# Internal class used where KML requires X and Y values and units
|
|
828
|
+
class KMLxy
|
|
829
|
+
attr_accessor :x, :y, :xunits, :yunits
|
|
830
|
+
def initialize(x = 0.5, y = 0.5, xunits = :fraction, yunits = :fraction)
|
|
831
|
+
@x = x
|
|
832
|
+
@y = y
|
|
833
|
+
@xunits = xunits
|
|
834
|
+
@yunits = yunits
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
def to_kml(name, indent = 0)
|
|
838
|
+
|
|
839
|
+
<<-kmlxy
|
|
840
|
+
#{ ' ' * indent}<#{ name } x="#{ @x }" y="#{ @y }" xunits="#{ @xunits }" yunits="#{ @yunits }" />
|
|
841
|
+
kmlxy
|
|
842
|
+
end
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
# Corresponds to the KML Icon object
|
|
846
|
+
class Icon
|
|
847
|
+
attr_accessor :href, :x, :y, :w, :h, :refreshMode, :refreshInterval, :viewRefreshMode, :viewRefreshTime, :viewBoundScale, :viewFormat, :httpQuery
|
|
848
|
+
|
|
849
|
+
def initialize(href = nil)
|
|
850
|
+
@href = href
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
def to_kml(indent = 0)
|
|
854
|
+
k = "#{ ' ' * indent }<Icon>\n"
|
|
855
|
+
k << kml_array([
|
|
856
|
+
[@href, 'href', true],
|
|
857
|
+
[@x, 'gx:x', true],
|
|
858
|
+
[@y, 'gx:y', true],
|
|
859
|
+
[@w, 'gx:w', true],
|
|
860
|
+
[@h, 'gx:h', true],
|
|
861
|
+
[@refreshMode, 'refreshMode', true],
|
|
862
|
+
[@refreshInterval, 'refreshInterval', true],
|
|
863
|
+
[@viewRefreshMode, 'viewRefreshMode', true],
|
|
864
|
+
[@viewBoundScale, 'viewBoundScale', true],
|
|
865
|
+
[@viewFormat, 'viewFormat', true],
|
|
866
|
+
[@httpQuery, 'httpQuery', true],
|
|
867
|
+
], indent + 4)
|
|
868
|
+
k << "#{ ' ' * indent }</Icon>\n"
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
# Corresponds to KML's IconStyle object.
|
|
873
|
+
class IconStyle < ColorStyle
|
|
874
|
+
attr_accessor :scale, :heading, :hotspot, :icon
|
|
875
|
+
|
|
876
|
+
def initialize(href, scale = 1, heading = 0, hs_x = 0.5, hs_y = 0.5, hs_xunits = :fraction, hs_yunits = :fraction, color = 'ffffffff', colormode = :normal)
|
|
877
|
+
super(color, colormode)
|
|
878
|
+
@scale = scale
|
|
879
|
+
@heading = heading
|
|
880
|
+
@icon = Icon.new(href) unless href.nil?
|
|
881
|
+
@hotspot = KMLxy.new(hs_x, hs_y, hs_xunits, hs_yunits) unless (hs_x.nil? and hs_y.nil? and hs_xunits.nil? and hs_yunits.nil?)
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def to_kml(indent = 0)
|
|
885
|
+
k = <<-iconstyle1
|
|
886
|
+
#{ ' ' * indent }<IconStyle id="#{@id}">
|
|
887
|
+
#{ super(indent + 4) }
|
|
888
|
+
iconstyle1
|
|
889
|
+
k << "#{ ' ' * indent } <scale>#{@scale}</scale>\n" unless @scale.nil?
|
|
890
|
+
k << "#{ ' ' * indent } <heading>#{@heading}</heading>\n" unless @heading.nil?
|
|
891
|
+
k << @icon.to_kml(indent + 4) unless @icon.nil?
|
|
892
|
+
k << "#{ ' ' * indent } <hotSpot x=\"#{@hotspot.x}\" y=\"#{@hotspot.y}\" xunits=\"#{@hotspot.xunits}\" yunits=\"#{@hotspot.yunits}\" />\n" unless @hotspot.nil?
|
|
893
|
+
k << "#{ ' ' * indent }</IconStyle>\n"
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
# Corresponds to KML's LabelStyle object
|
|
898
|
+
class LabelStyle < ColorStyle
|
|
899
|
+
attr_accessor :scale
|
|
900
|
+
|
|
901
|
+
def initialize(scale = 1, color = 'ffffffff', colormode = :normal)
|
|
902
|
+
super(color, colormode)
|
|
903
|
+
@scale = scale
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
def to_kml(indent = 0)
|
|
907
|
+
|
|
908
|
+
<<-labelstyle
|
|
909
|
+
#{ ' ' * indent }<LabelStyle id="#{@id}">
|
|
910
|
+
#{ super(indent + 4) }
|
|
911
|
+
#{ ' ' * indent } <scale>#{@scale}</scale>
|
|
912
|
+
#{ ' ' * indent }</LabelStyle>
|
|
913
|
+
labelstyle
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
# Corresponds to KML's LineStyle object. Color is stored as an 8-character hex
|
|
918
|
+
# string, with two characters each of alpha, blue, green, and red values, in
|
|
919
|
+
# that order, matching the ordering the KML spec demands.
|
|
920
|
+
class LineStyle < ColorStyle
|
|
921
|
+
attr_accessor :outercolor, :outerwidth, :physicalwidth, :width
|
|
922
|
+
|
|
923
|
+
def initialize(width = 1, outercolor = 'ffffffff', outerwidth = 0, physicalwidth = 0, color = 'ffffffff', colormode = :normal)
|
|
924
|
+
super(color, colormode)
|
|
925
|
+
@width = width
|
|
926
|
+
@outercolor = outercolor
|
|
927
|
+
@outerwidth = outerwidth
|
|
928
|
+
@physicalwidth = physicalwidth
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
def to_kml(indent = 0)
|
|
932
|
+
|
|
933
|
+
<<-linestyle
|
|
934
|
+
#{ ' ' * indent }<LineStyle id="#{@id}">
|
|
935
|
+
#{ super(indent + 4) }
|
|
936
|
+
#{ ' ' * indent } <width>#{@width}</width>
|
|
937
|
+
#{ ' ' * indent } <gx:outerColor>#{@outercolor}</gx:outerColor>
|
|
938
|
+
#{ ' ' * indent } <gx:outerWidth>#{@outerwidth}</gx:outerWidth>
|
|
939
|
+
#{ ' ' * indent } <gx:physicalWidth>#{@physicalwidth}</gx:physicalWidth>
|
|
940
|
+
#{ ' ' * indent }</LineStyle>
|
|
941
|
+
linestyle
|
|
942
|
+
end
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
# Corresponds to KML's ListStyle object. Color is stored as an 8-character hex
|
|
946
|
+
# string, with two characters each of alpha, blue, green, and red values, in
|
|
947
|
+
# that order, matching the ordering the KML spec demands.
|
|
948
|
+
class ListStyle < ColorStyle
|
|
949
|
+
attr_accessor :listitemtype, :bgcolor, :state, :href
|
|
950
|
+
|
|
951
|
+
def initialize(bgcolor = nil, state = nil, href = nil, listitemtype = nil)
|
|
952
|
+
super(nil, :normal)
|
|
953
|
+
@bgcolor = bgcolor
|
|
954
|
+
@state = state
|
|
955
|
+
@href = href
|
|
956
|
+
@listitemtype = listitemtype
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
def to_kml(indent = 0)
|
|
960
|
+
k = "#{ ' ' * indent }<ListStyle id=\"#{@id}\">\n"
|
|
961
|
+
k << kml_array([
|
|
962
|
+
[@listitemtype, 'listItemType', true],
|
|
963
|
+
[@bgcolor, 'bgColor', true]
|
|
964
|
+
], indent + 4)
|
|
965
|
+
if (! @state.nil? or ! @href.nil?) then
|
|
966
|
+
k << "#{ ' ' * indent } <ItemIcon>\n"
|
|
967
|
+
k << "#{ ' ' * indent } <state>#{@state}</state>\n" unless @state.nil?
|
|
968
|
+
k << "#{ ' ' * indent } <href>#{@href}</href>\n" unless @href.nil?
|
|
969
|
+
k << "#{ ' ' * indent } </ItemIcon>\n"
|
|
970
|
+
end
|
|
971
|
+
k << "#{ ' ' * indent }</ListStyle>\n"
|
|
972
|
+
k
|
|
973
|
+
end
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
# Corresponds to KML's PolyStyle object. Color is stored as an 8-character hex
|
|
977
|
+
# string, with two characters each of alpha, blue, green, and red values, in
|
|
978
|
+
# that order, matching the ordering the KML spec demands.
|
|
979
|
+
class PolyStyle < ColorStyle
|
|
980
|
+
attr_accessor :fill, :outline
|
|
981
|
+
|
|
982
|
+
def initialize(fill = 1, outline = 1, color = 'ffffffff', colormode = :normal)
|
|
983
|
+
super(color, colormode)
|
|
984
|
+
@fill = fill
|
|
985
|
+
@outline = outline
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
def to_kml(indent = 0)
|
|
989
|
+
|
|
990
|
+
k = <<-polystyle
|
|
991
|
+
#{ ' ' * indent }<PolyStyle id="#{@id}">
|
|
992
|
+
#{ super(indent + 4) }
|
|
993
|
+
#{ ' ' * indent } <fill>#{@fill}</fill>
|
|
994
|
+
polystyle
|
|
995
|
+
k << "#{ ' ' * indent } <outline>#{@outline}</outline>\n" unless @outline.nil?
|
|
996
|
+
k << "#{ ' ' * indent }</PolyStyle>\n"
|
|
997
|
+
k
|
|
998
|
+
end
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
# Abstract class corresponding to KML's StyleSelector object.
|
|
1002
|
+
class StyleSelector < KMLObject
|
|
1003
|
+
attr_accessor :attached
|
|
1004
|
+
def initialize
|
|
1005
|
+
super
|
|
1006
|
+
@attached = false
|
|
1007
|
+
Document.instance.styles << self
|
|
1008
|
+
end
|
|
1009
|
+
|
|
1010
|
+
def attached?
|
|
1011
|
+
@attached
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
def attach(obj)
|
|
1015
|
+
@attached = true
|
|
1016
|
+
obj.styles << self
|
|
1017
|
+
end
|
|
1018
|
+
end
|
|
1019
|
+
|
|
1020
|
+
# Corresponds to KML's Style object. Attributes are expected to be IconStyle,
|
|
1021
|
+
# LabelStyle, LineStyle, PolyStyle, BalloonStyle, and ListStyle objects.
|
|
1022
|
+
class Style < StyleSelector
|
|
1023
|
+
attr_accessor :icon, :label, :line, :poly, :balloon, :list
|
|
1024
|
+
def initialize(icon = nil, label = nil, line = nil, poly = nil, balloon = nil, list = nil)
|
|
1025
|
+
super()
|
|
1026
|
+
@icon = icon
|
|
1027
|
+
@label = label
|
|
1028
|
+
@line = line
|
|
1029
|
+
@poly = poly
|
|
1030
|
+
@balloon = balloon
|
|
1031
|
+
@list = list
|
|
1032
|
+
end
|
|
1033
|
+
|
|
1034
|
+
def to_kml(indent = 0)
|
|
1035
|
+
k = ''
|
|
1036
|
+
k << super + "#{ ' ' * indent }<Style id=\"#{@id}\">\n"
|
|
1037
|
+
k << @icon.to_kml(indent + 4) unless @icon.nil?
|
|
1038
|
+
k << @label.to_kml(indent + 4) unless @label.nil?
|
|
1039
|
+
k << @line.to_kml(indent + 4) unless @line.nil?
|
|
1040
|
+
k << @poly.to_kml(indent + 4) unless @poly.nil?
|
|
1041
|
+
k << @balloon.to_kml(indent + 4) unless @balloon.nil?
|
|
1042
|
+
k << @list.to_kml(indent + 4) unless @list.nil?
|
|
1043
|
+
k << "#{ ' ' * indent }</Style>\n"
|
|
1044
|
+
k
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
# Corresponds to KML's StyleMap object.
|
|
1049
|
+
class StyleMap < StyleSelector
|
|
1050
|
+
# StyleMap manages pairs. The first entry in each pair is a string key, the
|
|
1051
|
+
# second is either a Style or a styleUrl. It will be assumed to be the
|
|
1052
|
+
# latter if its kind_of? method doesn't claim it's a Style object
|
|
1053
|
+
def initialize(pairs = {})
|
|
1054
|
+
super()
|
|
1055
|
+
@pairs = pairs
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
# Adds a new Style to the StyleMap.
|
|
1059
|
+
def merge(a)
|
|
1060
|
+
@pairs.merge(a)
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
def to_kml(indent = 0)
|
|
1064
|
+
t = super + "#{ ' ' * indent }<StyleMap id=\"#{@id}\">\n"
|
|
1065
|
+
@pairs.each do |k, v|
|
|
1066
|
+
t << "#{ ' ' * indent } <Pair>\n"
|
|
1067
|
+
t << "#{ ' ' * indent } <key>#{ k }</key>\n"
|
|
1068
|
+
if v.kind_of? Style then
|
|
1069
|
+
t << ( ' ' * indent ) << v.to_kml(indent + 8)
|
|
1070
|
+
else
|
|
1071
|
+
t << "#{ ' ' * indent } <styleUrl>#{ v }</styleUrl>\n"
|
|
1072
|
+
end
|
|
1073
|
+
t << "#{ ' ' * indent } </Pair>\n"
|
|
1074
|
+
end
|
|
1075
|
+
t << "#{ ' ' * indent }</StyleMap>\n"
|
|
1076
|
+
t
|
|
1077
|
+
end
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
# Corresponds to KML's Placemark objects. The geometry attribute requires a
|
|
1081
|
+
# descendant of Geometry
|
|
1082
|
+
class Placemark < Feature
|
|
1083
|
+
attr_accessor :name, :geometry
|
|
1084
|
+
def initialize(name = nil, geo = nil)
|
|
1085
|
+
super(name)
|
|
1086
|
+
Document.instance.folder << self
|
|
1087
|
+
if geo.respond_to? '[]' then
|
|
1088
|
+
@geometry = geo
|
|
1089
|
+
else
|
|
1090
|
+
@geometry = [ geo ]
|
|
1091
|
+
end
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
def to_kml(indent = 0)
|
|
1095
|
+
a = "#{ ' ' * indent }<Placemark id=\"#{ @id }\">\n"
|
|
1096
|
+
a << super(indent + 4) {
|
|
1097
|
+
k = ''
|
|
1098
|
+
@geometry.each do |i| k << i.to_kml(indent + 4) unless i.nil? end
|
|
1099
|
+
k
|
|
1100
|
+
}
|
|
1101
|
+
a << "#{ ' ' * indent }</Placemark>\n"
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
def to_s
|
|
1105
|
+
"Placemark id #{ @id } named #{ @name }"
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
def longitude
|
|
1109
|
+
@geometry.longitude
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
def latitude
|
|
1113
|
+
@geometry.latitude
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
def altitude
|
|
1117
|
+
@geometry.altitude
|
|
1118
|
+
end
|
|
1119
|
+
|
|
1120
|
+
def altitudeMode
|
|
1121
|
+
@geometry.altitudeMode
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
def point
|
|
1125
|
+
if @geometry[0].kind_of? KMLPoint then
|
|
1126
|
+
@geometry[0]
|
|
1127
|
+
else
|
|
1128
|
+
raise "This placemark uses a non-point geometry, but the operation you're trying requires a point object"
|
|
1129
|
+
end
|
|
1130
|
+
end
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
# Abstract class corresponding to KML's gx:TourPrimitive object. Tours are made up
|
|
1134
|
+
# of descendants of these.
|
|
1135
|
+
class TourPrimitive < KMLObject
|
|
1136
|
+
def initialize
|
|
1137
|
+
Document.instance.tour << self
|
|
1138
|
+
end
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
# Cooresponds to KML's gx:FlyTo object. The @view parameter needs to look like an
|
|
1142
|
+
# AbstractView object
|
|
1143
|
+
class FlyTo < TourPrimitive
|
|
1144
|
+
attr_accessor :duration, :mode, :view
|
|
1145
|
+
|
|
1146
|
+
def initialize(view = nil, range = nil, duration = 0, mode = :bounce)
|
|
1147
|
+
@duration = duration
|
|
1148
|
+
@mode = mode
|
|
1149
|
+
if view.kind_of? AbstractView then
|
|
1150
|
+
@view = view
|
|
1151
|
+
else
|
|
1152
|
+
@view = LookAt.new(view)
|
|
1153
|
+
end
|
|
1154
|
+
if view.respond_to? 'range' and not range.nil? then
|
|
1155
|
+
@view.range = range
|
|
1156
|
+
end
|
|
1157
|
+
super()
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
def to_kml(indent = 0)
|
|
1161
|
+
k = super + "#{ ' ' * indent }<gx:FlyTo>\n"
|
|
1162
|
+
k << kml_array([
|
|
1163
|
+
[ @duration, 'gx:duration', true ],
|
|
1164
|
+
[ @mode, 'gx:flyToMode', true ]
|
|
1165
|
+
], indent + 4)
|
|
1166
|
+
k << @view.to_kml(indent + 4) unless @view.nil?
|
|
1167
|
+
k << "#{ ' ' * indent }</gx:FlyTo>\n"
|
|
1168
|
+
end
|
|
1169
|
+
end
|
|
1170
|
+
|
|
1171
|
+
# Corresponds to KML's gx:AnimatedUpdate object. For now at least, this isn't very
|
|
1172
|
+
# intelligent; you've got to manually craft the <Change> tag(s) within the
|
|
1173
|
+
# object.
|
|
1174
|
+
class AnimatedUpdate < TourPrimitive
|
|
1175
|
+
# For now, the user has to specify the change / create / delete elements in
|
|
1176
|
+
# the <Update> manually, rather than creating objects.
|
|
1177
|
+
attr_accessor :target, :delayedstart, :updates, :duration
|
|
1178
|
+
|
|
1179
|
+
# The updates argument is an array of strings containing <Change> elements
|
|
1180
|
+
def initialize(updates = [], duration = 0, target = '', delayedstart = nil)
|
|
1181
|
+
super()
|
|
1182
|
+
begin
|
|
1183
|
+
raise "incorrect object type" unless @target.kind_of? KMLObject
|
|
1184
|
+
@target = target.id
|
|
1185
|
+
rescue RuntimeError
|
|
1186
|
+
@target = target
|
|
1187
|
+
end
|
|
1188
|
+
@updates = updates
|
|
1189
|
+
@duration = duration
|
|
1190
|
+
@delayedstart = delayedstart
|
|
1191
|
+
end
|
|
1192
|
+
|
|
1193
|
+
# Adds another update string, presumably containing a <Change> element
|
|
1194
|
+
def <<(a)
|
|
1195
|
+
@updates << a << "\n"
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
def to_kml(indent = 0)
|
|
1199
|
+
k = super + <<-animatedupdate_kml
|
|
1200
|
+
#{ ' ' * indent }<gx:AnimatedUpdate>
|
|
1201
|
+
#{ ' ' * indent } <gx:duration>#{@duration}</gx:duration>
|
|
1202
|
+
animatedupdate_kml
|
|
1203
|
+
k << "#{ ' ' * indent } <gx:delayeStart>#{@delayedstart}</gx:delayedStart>\n" unless @delayedstart.nil?
|
|
1204
|
+
k << "#{ ' ' * indent } <Update>\n"
|
|
1205
|
+
k << "#{ ' ' * indent } <targetHref>#{@target}</targetHref>\n"
|
|
1206
|
+
k << "#{ ' ' * indent } " << @updates.join("\n#{ ' ' * (indent + 1) }")
|
|
1207
|
+
k << "#{ ' ' * indent } </Update>\n#{ ' ' * indent }</gx:AnimatedUpdate>\n"
|
|
1208
|
+
k
|
|
1209
|
+
end
|
|
1210
|
+
end
|
|
1211
|
+
|
|
1212
|
+
# Corresponds to a KML gx:TourControl object
|
|
1213
|
+
class TourControl < TourPrimitive
|
|
1214
|
+
def initialize
|
|
1215
|
+
super
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
def to_kml(indent = 0)
|
|
1219
|
+
k = "#{ ' ' * indent }<gx:TourControl id=\"#{ @id }\">\n"
|
|
1220
|
+
k << "#{ ' ' * indent } <gx:playMode>pause</gx:playMode>\n"
|
|
1221
|
+
k << "#{ ' ' * indent }</gx:TourControl>\n"
|
|
1222
|
+
end
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
# Corresponds to a KML gx:Wait object
|
|
1226
|
+
class Wait < TourPrimitive
|
|
1227
|
+
attr_accessor :duration
|
|
1228
|
+
def initialize(duration = 0)
|
|
1229
|
+
super()
|
|
1230
|
+
@duration = duration
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
def to_kml(indent = 0)
|
|
1234
|
+
super + <<-wait_kml
|
|
1235
|
+
#{ ' ' * indent }<gx:Wait><gx:duration>#{@duration}</gx:duration></gx:Wait>
|
|
1236
|
+
wait_kml
|
|
1237
|
+
end
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1240
|
+
# Corresponds to a KML gx:SoundCue object
|
|
1241
|
+
class SoundCue < TourPrimitive
|
|
1242
|
+
attr_accessor :href, :delayedStart
|
|
1243
|
+
def initialize(href, delayedStart = nil)
|
|
1244
|
+
super()
|
|
1245
|
+
@href = href
|
|
1246
|
+
@delayedStart = delayedStart
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
def to_kml(indent = 0)
|
|
1250
|
+
k = "#{ ' ' * indent }<gx:SoundCue id=\"#{ @id }\">\n"
|
|
1251
|
+
k << "#{ ' ' * indent } <href>#{ @href }</href>\n"
|
|
1252
|
+
k << "#{ ' ' * indent } <gx:delayedStart>#{ @delayedStart }</gx:delayedStart>\n" unless @delayedStart.nil?
|
|
1253
|
+
k << "#{ ' ' * indent}</gx:SoundCue>\n"
|
|
1254
|
+
end
|
|
1255
|
+
end
|
|
1256
|
+
|
|
1257
|
+
# Corresponds to a KML gx:Tour object
|
|
1258
|
+
class Tour < KMLObject
|
|
1259
|
+
attr_accessor :name, :description, :last_abs_view
|
|
1260
|
+
def initialize(name = nil, description = nil)
|
|
1261
|
+
super()
|
|
1262
|
+
@name = name
|
|
1263
|
+
@description = description
|
|
1264
|
+
@items = []
|
|
1265
|
+
end
|
|
1266
|
+
|
|
1267
|
+
# Add another element to this Tour
|
|
1268
|
+
def <<(a)
|
|
1269
|
+
@items << a
|
|
1270
|
+
@last_abs_view = a.view if a.kind_of? FlyTo
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
def to_kml(indent = 0)
|
|
1274
|
+
k = super + "#{ ' ' * indent }<gx:Tour id=\"#{ @id }\">\n"
|
|
1275
|
+
k << kml_array([
|
|
1276
|
+
[ @name, 'name', true ],
|
|
1277
|
+
[ @description, 'description', true ],
|
|
1278
|
+
], indent + 4)
|
|
1279
|
+
k << "#{ ' ' * indent } <gx:Playlist>\n";
|
|
1280
|
+
|
|
1281
|
+
@items.map do |a| k << a.to_kml(indent + 8) << "\n" end
|
|
1282
|
+
|
|
1283
|
+
k << "#{ ' ' * indent } </gx:Playlist>\n"
|
|
1284
|
+
k << "#{ ' ' * indent }</gx:Tour>\n"
|
|
1285
|
+
k
|
|
1286
|
+
end
|
|
1287
|
+
end
|
|
1288
|
+
|
|
1289
|
+
# Abstract class corresponding to the KML Overlay object
|
|
1290
|
+
class Overlay < Feature
|
|
1291
|
+
attr_accessor :color, :drawOrder, :icon
|
|
1292
|
+
|
|
1293
|
+
def initialize(icon, name = nil)
|
|
1294
|
+
super(name)
|
|
1295
|
+
if icon.respond_to?('to_kml') then
|
|
1296
|
+
@icon = icon
|
|
1297
|
+
elsif not icon.nil?
|
|
1298
|
+
@icon = Icon.new(icon.to_s)
|
|
1299
|
+
end
|
|
1300
|
+
Document.instance.folder << self
|
|
1301
|
+
end
|
|
1302
|
+
|
|
1303
|
+
def to_kml(indent = 0)
|
|
1304
|
+
k = super(indent) + kml_array([
|
|
1305
|
+
[ @color, 'color', true ],
|
|
1306
|
+
[ @drawOrder, 'drawOrder', true ],
|
|
1307
|
+
], indent + 4)
|
|
1308
|
+
k << @icon.to_kml(indent) unless @icon.nil?
|
|
1309
|
+
k
|
|
1310
|
+
end
|
|
1311
|
+
end
|
|
1312
|
+
|
|
1313
|
+
# Corresponds to KML's ScreenOverlay object
|
|
1314
|
+
class ScreenOverlay < Overlay
|
|
1315
|
+
attr_accessor :overlayXY, :screenXY, :rotationXY, :size, :rotation
|
|
1316
|
+
def initialize(icon, name = nil, size = nil, rotation = nil, overlayXY = nil, screenXY = nil, rotationXY = nil)
|
|
1317
|
+
super(icon, name)
|
|
1318
|
+
@overlayXY = overlayXY
|
|
1319
|
+
@screenXY = screenXY
|
|
1320
|
+
@rotationXY = rotationXY
|
|
1321
|
+
@size = size
|
|
1322
|
+
@rotation = rotation
|
|
1323
|
+
end
|
|
1324
|
+
|
|
1325
|
+
def to_kml(indent = 0)
|
|
1326
|
+
k = "#{ ' ' * indent }<ScreenOverlay id=\"#{ @id }\">\n"
|
|
1327
|
+
k << super(indent + 4)
|
|
1328
|
+
k << @overlayXY.to_kml('overlayXY', indent + 4) unless @overlayXY.nil?
|
|
1329
|
+
k << @screenXY.to_kml('screenXY', indent + 4) unless @screenXY.nil?
|
|
1330
|
+
k << @rotationXY.to_kml('rotationXY', indent + 4) unless @rotationXY.nil?
|
|
1331
|
+
k << @size.to_kml('size', indent + 4) unless @size.nil?
|
|
1332
|
+
k << "#{ ' ' * indent } <rotation>#{ @rotation }</rotation>\n" unless @rotation.nil?
|
|
1333
|
+
k << "#{ ' ' * indent }</ScreenOverlay>\n"
|
|
1334
|
+
end
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
# Supporting object for the PhotoOverlay class
|
|
1338
|
+
class ViewVolume
|
|
1339
|
+
attr_accessor :leftFov, :rightFov, :bottomFov, :topFov, :near
|
|
1340
|
+
def initialize(near, leftFov = -45, rightFov = 45, bottomFov = -45, topFov = 45)
|
|
1341
|
+
@leftFov = leftFov
|
|
1342
|
+
@rightFov = rightFov
|
|
1343
|
+
@bottomFov = bottomFov
|
|
1344
|
+
@topFov = topFov
|
|
1345
|
+
@near = near
|
|
1346
|
+
end
|
|
1347
|
+
|
|
1348
|
+
def to_kml(indent = 0)
|
|
1349
|
+
|
|
1350
|
+
<<-viewvolume
|
|
1351
|
+
#{ ' ' * indent }<ViewVolume>
|
|
1352
|
+
#{ ' ' * indent } <near>#{@near}</near>
|
|
1353
|
+
#{ ' ' * indent } <leftFov>#{@leftFov}</leftFov>
|
|
1354
|
+
#{ ' ' * indent } <rightFov>#{@rightFov}</rightFov>
|
|
1355
|
+
#{ ' ' * indent } <bottomFov>#{@bottomFov}</bottomFov>
|
|
1356
|
+
#{ ' ' * indent } <topFov>#{@topFov}</topFov>
|
|
1357
|
+
#{ ' ' * indent }</ViewVolume>
|
|
1358
|
+
viewvolume
|
|
1359
|
+
end
|
|
1360
|
+
end
|
|
1361
|
+
|
|
1362
|
+
# Supporting object for the PhotoOverlay class
|
|
1363
|
+
class ImagePyramid
|
|
1364
|
+
attr_accessor :tileSize, :maxWidth, :maxHeight, :gridOrigin
|
|
1365
|
+
|
|
1366
|
+
def initialize(maxWidth, maxHeight, gridOrigin, tileSize = 256)
|
|
1367
|
+
@tileSize = tileSize
|
|
1368
|
+
@maxWidth = maxWidth
|
|
1369
|
+
@maxHeight = maxHeight
|
|
1370
|
+
@gridOrigin = gridOrigin
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
def to_kml(indent = 0)
|
|
1374
|
+
|
|
1375
|
+
<<-imagepyramid
|
|
1376
|
+
#{ ' ' * indent }<ImagePyramid>
|
|
1377
|
+
#{ ' ' * indent } <tileSize>#{@tileSize}</tileSize>
|
|
1378
|
+
#{ ' ' * indent } <maxWidth>#{@maxWidth}</maxWidth>
|
|
1379
|
+
#{ ' ' * indent } <maxHeight>#{@maxHeight}</maxHeight>
|
|
1380
|
+
#{ ' ' * indent } <gridOrigin>#{@gridOrigin}</gridOrigin>
|
|
1381
|
+
#{ ' ' * indent }</ImagePyramid>
|
|
1382
|
+
imagepyramid
|
|
1383
|
+
end
|
|
1384
|
+
end
|
|
1385
|
+
|
|
1386
|
+
# Corresponds to KML's PhotoOverlay class
|
|
1387
|
+
class PhotoOverlay < Overlay
|
|
1388
|
+
attr_accessor :rotation, :viewvolume, :imagepyramid, :point, :shape
|
|
1389
|
+
|
|
1390
|
+
def initialize(icon, point, rotation = 0, viewvolume = nil, imagepyramid = nil, shape = :rectangle)
|
|
1391
|
+
super(icon)
|
|
1392
|
+
if point.respond_to?('point')
|
|
1393
|
+
@point = point.point
|
|
1394
|
+
else
|
|
1395
|
+
@point = point
|
|
1396
|
+
end
|
|
1397
|
+
@rotation = rotation
|
|
1398
|
+
@viewVolume = viewvolume
|
|
1399
|
+
@imagePyramid = imagepyramid
|
|
1400
|
+
@shape = shape
|
|
1401
|
+
end
|
|
1402
|
+
|
|
1403
|
+
def to_kml(indent = 0)
|
|
1404
|
+
k = "#{ ' ' * indent }<PhotoOverlay>\n"
|
|
1405
|
+
k << super(indent + 4)
|
|
1406
|
+
k << @viewVolume.to_kml(indent + 4) unless @viewVolume.nil?
|
|
1407
|
+
k << @imagePyramid.to_kml(indent + 4) unless @imagePyramid.nil?
|
|
1408
|
+
k << @point.to_kml(indent + 4, true)
|
|
1409
|
+
k << "#{ ' ' * indent } <rotation>#{ @rotation }</rotation>\n"
|
|
1410
|
+
k << "#{ ' ' * indent } <shape>#{ @shape }</shape>\n"
|
|
1411
|
+
k << "#{ ' ' * indent }</PhotoOverlay>\n"
|
|
1412
|
+
end
|
|
1413
|
+
end
|
|
1414
|
+
|
|
1415
|
+
# Corresponds to KML's LatLonBox and LatLonAltBox
|
|
1416
|
+
class LatLonBox
|
|
1417
|
+
attr_reader :north, :south, :east, :west
|
|
1418
|
+
attr_accessor :rotation, :minAltitude, :maxAltitude, :altitudeMode
|
|
1419
|
+
|
|
1420
|
+
def initialize(north, south, east, west, rotation = 0, minAltitude = nil, maxAltitude = nil, altitudeMode = :clampToGround)
|
|
1421
|
+
@north = convert_coord north
|
|
1422
|
+
@south = convert_coord south
|
|
1423
|
+
@east = convert_coord east
|
|
1424
|
+
@west = convert_coord west
|
|
1425
|
+
@minAltitude = minAltitude
|
|
1426
|
+
@maxAltitude = maxAltitude
|
|
1427
|
+
@altitudeMode = altitudeMode
|
|
1428
|
+
@rotation = rotation
|
|
1429
|
+
end
|
|
1430
|
+
|
|
1431
|
+
def north=(a)
|
|
1432
|
+
@north = convert_coord a
|
|
1433
|
+
end
|
|
1434
|
+
|
|
1435
|
+
def south=(a)
|
|
1436
|
+
@south = convert_coord a
|
|
1437
|
+
end
|
|
1438
|
+
|
|
1439
|
+
def east=(a)
|
|
1440
|
+
@east = convert_coord a
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1443
|
+
def west=(a)
|
|
1444
|
+
@west = convert_coord a
|
|
1445
|
+
end
|
|
1446
|
+
|
|
1447
|
+
def to_kml(indent = 0, alt = false)
|
|
1448
|
+
name = alt ? 'LatLonAltBox' : 'LatLonBox'
|
|
1449
|
+
k = <<-latlonbox
|
|
1450
|
+
#{ ' ' * indent }<#{ name }>
|
|
1451
|
+
#{ ' ' * indent } <north>#{ @north }</north>
|
|
1452
|
+
#{ ' ' * indent } <south>#{ @south }</south>
|
|
1453
|
+
#{ ' ' * indent } <east>#{ @east }</east>
|
|
1454
|
+
#{ ' ' * indent } <west>#{ @west }</west>
|
|
1455
|
+
latlonbox
|
|
1456
|
+
k << "#{ ' ' * indent } <minAltitude>#{ @minAltitude }</minAltitude>\n" unless @minAltitude.nil?
|
|
1457
|
+
k << "#{ ' ' * indent } <maxAltitude>#{ @maxAltitude }</maxAltitude>\n" unless @maxAltitude.nil?
|
|
1458
|
+
if (not @minAltitude.nil? or not @maxAltitude.nil?) then
|
|
1459
|
+
if @altitudeMode == :clampToGround or @altitudeMode == :relativeToGround or @altitudeMode == :absolute then
|
|
1460
|
+
altitudeModeString = "#{ ' ' * indent } <altitudeMode>#{ @altitudeMode }</altitudeMode>\n"
|
|
1461
|
+
else
|
|
1462
|
+
altitudeModeString = "#{ ' ' * indent } <gx:altitudeMode>#{ @altitudeMode }</gx:altitudeMode>\n"
|
|
1463
|
+
end
|
|
1464
|
+
end
|
|
1465
|
+
k << <<-latlonbox2
|
|
1466
|
+
#{ ' ' * indent } <rotation>#{ @rotation }</rotation>
|
|
1467
|
+
#{ ' ' * indent }</#{ name }>
|
|
1468
|
+
latlonbox2
|
|
1469
|
+
end
|
|
1470
|
+
end
|
|
1471
|
+
|
|
1472
|
+
# Corresponds to KML's gx:LatLonQuad object
|
|
1473
|
+
class LatLonQuad
|
|
1474
|
+
attr_accessor :lowerLeft, :lowerRight, :upperRight, :upperLeft
|
|
1475
|
+
def initialize(lowerLeft, lowerRight, upperRight, upperLeft)
|
|
1476
|
+
@lowerLeft = lowerLeft
|
|
1477
|
+
@lowerRight = lowerRight
|
|
1478
|
+
@upperRight = upperRight
|
|
1479
|
+
@upperLeft = upperLeft
|
|
1480
|
+
end
|
|
1481
|
+
|
|
1482
|
+
def to_kml(indent = 0)
|
|
1483
|
+
|
|
1484
|
+
<<-latlonquad
|
|
1485
|
+
#{ ' ' * indent }<gx:LatLonQuad>
|
|
1486
|
+
#{ ' ' * indent } <coordinates>#{ @lowerLeft.longitude },#{ @lowerLeft.latitude } #{ @lowerRight.longitude },#{ @lowerRight.latitude } #{ @upperRight.longitude },#{ @upperRight.latitude } #{ @upperLeft.longitude },#{ @upperLeft.latitude }</coordinates>
|
|
1487
|
+
#{ ' ' * indent }</gx:LatLonQuad>
|
|
1488
|
+
latlonquad
|
|
1489
|
+
end
|
|
1490
|
+
end
|
|
1491
|
+
|
|
1492
|
+
# Corresponds to KML's GroundOverlay object
|
|
1493
|
+
class GroundOverlay < Overlay
|
|
1494
|
+
attr_accessor :altitude, :altitudeMode, :latlonbox, :latlonquad
|
|
1495
|
+
def initialize(icon, latlonbox = nil, latlonquad = nil, altitude = 0, altitudeMode = :clampToGround)
|
|
1496
|
+
super(icon)
|
|
1497
|
+
@latlonbox = latlonbox
|
|
1498
|
+
@latlonquad = latlonquad
|
|
1499
|
+
@altitude = altitude
|
|
1500
|
+
@altitudeMode = altitudeMode
|
|
1501
|
+
end
|
|
1502
|
+
|
|
1503
|
+
def to_kml(indent = 0)
|
|
1504
|
+
raise "Either latlonbox or latlonquad must be non-nil" if @latlonbox.nil? and @latlonquad.nil?
|
|
1505
|
+
|
|
1506
|
+
k = "#{ ' ' * indent}<GroundOverlay id=\"#{ @id }\">\n"
|
|
1507
|
+
k << super(indent + 4)
|
|
1508
|
+
k << "#{ ' ' * indent } <altitude>#{ @altitude }</altitude>\n"
|
|
1509
|
+
k << ' ' * indent
|
|
1510
|
+
if @altitudeMode == :clampToGround or @altitudeMode == :relativeToGround or @altitudeMode == :absolute then
|
|
1511
|
+
k << "#{ ' ' * indent } <altitudeMode>#{ @altitudeMode }</altitudeMode>\n"
|
|
1512
|
+
else
|
|
1513
|
+
k << "#{ ' ' * indent } <gx:altitudeMode>#{ @altitudeMode }</gx:altitudeMode>\n"
|
|
1514
|
+
end
|
|
1515
|
+
k << @latlonbox.to_kml(indent + 4) unless @latlonbox.nil?
|
|
1516
|
+
k << @latlonquad.to_kml(indent + 4) unless @latlonquad.nil?
|
|
1517
|
+
k << "#{ ' ' * indent }</GroundOverlay>\n"
|
|
1518
|
+
k
|
|
1519
|
+
end
|
|
1520
|
+
end
|
|
1521
|
+
|
|
1522
|
+
# Corresponds to the LOD (Level of Detail) object
|
|
1523
|
+
class Lod
|
|
1524
|
+
attr_accessor :minpixels, :maxpixels, :minfade, :maxfade
|
|
1525
|
+
def initialize(minpixels, maxpixels, minfade, maxfade)
|
|
1526
|
+
@minpixels = minpixels
|
|
1527
|
+
@maxpixels = maxpixels
|
|
1528
|
+
@minfade = minfade
|
|
1529
|
+
@maxfade = maxfade
|
|
1530
|
+
end
|
|
1531
|
+
|
|
1532
|
+
def to_kml(indent = 0)
|
|
1533
|
+
|
|
1534
|
+
<<-lod
|
|
1535
|
+
#{ ' ' * indent }<Lod>
|
|
1536
|
+
#{ ' ' * indent } <minLodPixels>#{ @minpixels }</minLodPixels>
|
|
1537
|
+
#{ ' ' * indent } <maxLodPixels>#{ @maxpixels }</maxLodPixels>
|
|
1538
|
+
#{ ' ' * indent } <minFadeExtent>#{ @minfade }</minFadeExtent>
|
|
1539
|
+
#{ ' ' * indent } <maxFadeExtent>#{ @maxfade }</maxFadeExtent>
|
|
1540
|
+
#{ ' ' * indent }</Lod>
|
|
1541
|
+
lod
|
|
1542
|
+
end
|
|
1543
|
+
end
|
|
1544
|
+
|
|
1545
|
+
# Corresponds to the KML Region object
|
|
1546
|
+
class Region < KMLObject
|
|
1547
|
+
attr_accessor :latlonaltbox, :lod
|
|
1548
|
+
|
|
1549
|
+
def initialize(latlonaltbox, lod)
|
|
1550
|
+
super()
|
|
1551
|
+
@latlonaltbox = latlonaltbox
|
|
1552
|
+
@lod = lod
|
|
1553
|
+
end
|
|
1554
|
+
|
|
1555
|
+
def to_kml(indent = 0)
|
|
1556
|
+
k = "#{' ' * indent}<Region id=\"#{@id}\">\n"
|
|
1557
|
+
k << @latlonaltbox.to_kml(indent + 4, true) unless @latlonaltbox.nil?
|
|
1558
|
+
k << @lod.to_kml(indent + 4) unless @lod.nil?
|
|
1559
|
+
k << "#{' ' * indent}</Region>\n"
|
|
1560
|
+
k
|
|
1561
|
+
end
|
|
1562
|
+
end
|
|
1563
|
+
|
|
1564
|
+
# Sub-object in the KML Model class
|
|
1565
|
+
class Orientation
|
|
1566
|
+
attr_accessor :heading, :tilt, :roll
|
|
1567
|
+
def initialize(heading, tilt, roll)
|
|
1568
|
+
@heading = heading
|
|
1569
|
+
# Although the KML reference by Google is clear on these ranges, Google Earth
|
|
1570
|
+
# supports values outside the ranges, and sometimes it's useful to use
|
|
1571
|
+
# them. So I'm turning off this error checking
|
|
1572
|
+
# raise "Heading should be between 0 and 360 inclusive; you gave #{ heading }" unless @heading <= 360 and @heading >= 0
|
|
1573
|
+
@tilt = tilt
|
|
1574
|
+
# raise "Tilt should be between 0 and 180 inclusive; you gave #{ tilt }" unless @tilt <= 180 and @tilt >= 0
|
|
1575
|
+
@roll = roll
|
|
1576
|
+
# raise "Roll should be between 0 and 180 inclusive; you gave #{ roll }" unless @roll <= 180 and @roll >= 0
|
|
1577
|
+
end
|
|
1578
|
+
|
|
1579
|
+
def to_kml(indent = 0)
|
|
1580
|
+
k = "#{ ' ' * indent }<Orientation>\n"
|
|
1581
|
+
k << "#{ ' ' * indent } <heading>#{ @heading }</heading>\n"
|
|
1582
|
+
k << "#{ ' ' * indent } <tilt>#{ @tilt }</tilt>\n"
|
|
1583
|
+
k << "#{ ' ' * indent } <roll>#{ @roll }</roll>\n"
|
|
1584
|
+
k << "#{ ' ' * indent }</Orientation>\n"
|
|
1585
|
+
k
|
|
1586
|
+
end
|
|
1587
|
+
end
|
|
1588
|
+
|
|
1589
|
+
# Sub-object in the KML Model class
|
|
1590
|
+
class Scale
|
|
1591
|
+
attr_accessor :x, :y, :z
|
|
1592
|
+
def initialize(x, y, z = 1)
|
|
1593
|
+
@x = x
|
|
1594
|
+
@y = y
|
|
1595
|
+
@z = z
|
|
1596
|
+
end
|
|
1597
|
+
|
|
1598
|
+
def to_kml(indent = 0)
|
|
1599
|
+
k = "#{ ' ' * indent }<Scale>\n"
|
|
1600
|
+
k << "#{ ' ' * indent } <x>#{ x }</x>\n"
|
|
1601
|
+
k << "#{ ' ' * indent } <y>#{ y }</y>\n"
|
|
1602
|
+
k << "#{ ' ' * indent } <z>#{ z }</z>\n"
|
|
1603
|
+
k << "#{ ' ' * indent }</Scale>\n"
|
|
1604
|
+
end
|
|
1605
|
+
end
|
|
1606
|
+
|
|
1607
|
+
# Sub-object in the KML ResourceMap class
|
|
1608
|
+
class Alias
|
|
1609
|
+
attr_accessor :targetHref, :sourceHref
|
|
1610
|
+
def initialize(targetHref = nil, sourceHref = nil)
|
|
1611
|
+
@targetHref = targetHref
|
|
1612
|
+
@sourceHref = sourceHref
|
|
1613
|
+
end
|
|
1614
|
+
|
|
1615
|
+
def to_kml(indent = 0)
|
|
1616
|
+
k = "#{ ' ' * indent }<Alias>\n"
|
|
1617
|
+
k << "#{ ' ' * indent } <targetHref>#{ @targetHref }</targetHref>\n"
|
|
1618
|
+
k << "#{ ' ' * indent } <sourceHref>#{ @sourceHref }</sourceHref>\n"
|
|
1619
|
+
k << "#{ ' ' * indent }</Alias>\n"
|
|
1620
|
+
k
|
|
1621
|
+
end
|
|
1622
|
+
end
|
|
1623
|
+
|
|
1624
|
+
# Sub-object in the KML Model class
|
|
1625
|
+
class ResourceMap
|
|
1626
|
+
attr_accessor :aliases
|
|
1627
|
+
def initialize(aliases = [])
|
|
1628
|
+
@aliases = []
|
|
1629
|
+
if not aliases.nil? then
|
|
1630
|
+
if aliases.kind_of? Enumerable then
|
|
1631
|
+
@aliases += aliases
|
|
1632
|
+
else
|
|
1633
|
+
@aliases << aliases
|
|
1634
|
+
end
|
|
1635
|
+
end
|
|
1636
|
+
end
|
|
1637
|
+
|
|
1638
|
+
def to_kml(indent = 0)
|
|
1639
|
+
return '' if @aliases.size == 0
|
|
1640
|
+
k = "#{ ' ' * indent }<ResourceMap>\n"
|
|
1641
|
+
k << "#{ ' ' * indent }</ResourceMap>\n"
|
|
1642
|
+
@aliases.each do |a| k << a.to_kml(indent + 4) end
|
|
1643
|
+
k
|
|
1644
|
+
end
|
|
1645
|
+
end
|
|
1646
|
+
|
|
1647
|
+
# Corresponds to KML's Link object
|
|
1648
|
+
class Link < KMLObject
|
|
1649
|
+
attr_accessor :href, :refreshMode, :refreshInterval, :viewRefreshMode, :viewBoundScale, :viewFormat, :httpQuery
|
|
1650
|
+
def initialize(href = '', refreshMode = :onChange, viewRefreshMode = :never)
|
|
1651
|
+
super()
|
|
1652
|
+
@href = href
|
|
1653
|
+
@refreshMode = refreshMode
|
|
1654
|
+
@viewRefreshMode = viewRefreshMode
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
def to_kml(indent = 0)
|
|
1658
|
+
k = "#{ ' ' * indent }<Link id=\"#{ @id }\">\n"
|
|
1659
|
+
k << "#{ ' ' * indent } <href>#{ @href }</href>\n"
|
|
1660
|
+
k << "#{ ' ' * indent } <refreshMode>#{ @refreshMode }</refreshMode>\n"
|
|
1661
|
+
k << "#{ ' ' * indent } <viewRefreshMode>#{ @viewRefreshMode }</viewRefreshMode>\n"
|
|
1662
|
+
k << "#{ ' ' * indent } <refreshInterval>#{ @refreshInterval }</refreshInterval>\n" unless @refreshInterval.nil?
|
|
1663
|
+
k << "#{ ' ' * indent } <viewBoundScale>#{ @viewBoundScale }</viewBoundScale>\n" unless @viewBoundScale.nil?
|
|
1664
|
+
k << "#{ ' ' * indent } <viewFormat>#{ @viewFormat }</viewFormat>\n" unless @viewFormat.nil?
|
|
1665
|
+
k << "#{ ' ' * indent } <httpQuery>#{ @httpQuery }</httpQuery>\n" unless @httpQuery.nil?
|
|
1666
|
+
k << "#{ ' ' * indent }</Link>\n"
|
|
1667
|
+
k
|
|
1668
|
+
end
|
|
1669
|
+
end
|
|
1670
|
+
|
|
1671
|
+
# Corresponds to the KML Model class
|
|
1672
|
+
class Model < Geometry
|
|
1673
|
+
attr_accessor :link, :location, :orientation, :scale, :resourceMap
|
|
1674
|
+
|
|
1675
|
+
# location should be a KMLPoint, or some object that can behave like one,
|
|
1676
|
+
# including a Placemark. Model will get its Location and altitudeMode data
|
|
1677
|
+
# from this attribute
|
|
1678
|
+
def initialize(link, location, orientation, scale, resourceMap)
|
|
1679
|
+
super()
|
|
1680
|
+
@link = link
|
|
1681
|
+
@location = location
|
|
1682
|
+
@orientation = orientation
|
|
1683
|
+
@scale = scale
|
|
1684
|
+
@resourceMap = resourceMap
|
|
1685
|
+
end
|
|
1686
|
+
|
|
1687
|
+
def to_kml(indent = 0)
|
|
1688
|
+
k = "#{ ' ' * indent }<Model id=\"#{ @id }\">\n"
|
|
1689
|
+
k << @link.to_kml(indent + 4)
|
|
1690
|
+
if @location.altitudeMode == :clampToGround or @location.altitudeMode == :relativeToGround or @location.altitudeMode == :absolute then
|
|
1691
|
+
k << "#{ ' ' * indent } <altitudeMode>#{ @location.altitudeMode }</altitudeMode>\n"
|
|
1692
|
+
else
|
|
1693
|
+
k << "#{ ' ' * indent } <gx:altitudeMode>#{ @location.altitudeMode }</gx:altitudeMode>\n"
|
|
1694
|
+
end
|
|
1695
|
+
k << "#{ ' ' * indent } <Location>\n"
|
|
1696
|
+
k << "#{ ' ' * indent } <longitude>#{ @location.longitude }</longitude>\n"
|
|
1697
|
+
k << "#{ ' ' * indent } <latitude>#{ @location.latitude }</latitude>\n"
|
|
1698
|
+
k << "#{ ' ' * indent } <altitude>#{ @location.altitude }</altitude>\n"
|
|
1699
|
+
k << "#{ ' ' * indent } </Location>\n"
|
|
1700
|
+
k << @orientation.to_kml(indent + 4)
|
|
1701
|
+
k << @scale.to_kml(indent + 4)
|
|
1702
|
+
k << @resourceMap.to_kml(indent + 4)
|
|
1703
|
+
k << "#{ ' ' * indent }</Model>\n"
|
|
1704
|
+
k
|
|
1705
|
+
end
|
|
1706
|
+
end
|