kamelopard 0.0.13 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/kamelopard/classes.rb +128 -20
- data/lib/kamelopard/function.rb +1 -0
- data/lib/kamelopard/function_paths.rb +6 -10
- data/lib/kamelopard/geocode.rb +1 -0
- data/lib/kamelopard/helpers.rb +98 -41
- data/lib/kamelopard/multicam.rb +2 -1
- data/lib/kamelopard/multicamify.rb +1 -0
- data/lib/kamelopard/regionate.rb +99 -0
- data/lib/kamelopard/spline.rb +2 -1
- metadata +4 -3
data/lib/kamelopard/classes.rb
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
|
9
9
|
# Pretty much everything important is in this module
|
10
10
|
module Kamelopard
|
11
|
+
require 'bundler/setup'
|
11
12
|
require 'singleton'
|
12
13
|
require 'xml'
|
13
14
|
require 'yaml'
|
@@ -57,6 +58,26 @@ module Kamelopard
|
|
57
58
|
@@logger.call(level, mod, msg) unless @@logger.nil? or @@log_level > LogLevels[level]
|
58
59
|
end
|
59
60
|
|
61
|
+
def Kamelopard.xml_to_hash(node, fields)
|
62
|
+
result = {}
|
63
|
+
fields.each do |field|
|
64
|
+
begin
|
65
|
+
if field =~ /:/
|
66
|
+
xmlfield = field
|
67
|
+
else
|
68
|
+
xmlfield = "kml:#{field}"
|
69
|
+
end
|
70
|
+
my_f = node.find("//#{xmlfield}").first
|
71
|
+
if ! my_f.nil?
|
72
|
+
result[field.to_sym] = my_f.first
|
73
|
+
end
|
74
|
+
rescue NoMethodError
|
75
|
+
log(:debug, 'xml-to-hash', "Error getting field #{field} (#{xmlfileld}) from node")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
result
|
79
|
+
end
|
80
|
+
|
60
81
|
def Kamelopard.get_next_id # :nodoc:
|
61
82
|
@@sequence += 1
|
62
83
|
@@sequence
|
@@ -318,6 +339,13 @@ module Kamelopard
|
|
318
339
|
@altitude = altitude unless altitude.nil?
|
319
340
|
end
|
320
341
|
|
342
|
+
# Converts an XML::Node to a Kamelopard::Point
|
343
|
+
def self.parse(p)
|
344
|
+
a = Kamelopard.xml_to_hash(p, %w[altitudeMode extrude coordinates])
|
345
|
+
(lon, lat, alt) = a[:coordinates].to_s.split(/,\s+/).collect { |a| a.to_f }
|
346
|
+
return Point.new(lon, lat, alt, :altitudeMode => a[:altitudeMode].to_s.to_sym, :extrude => a[:extrude].to_s.to_i)
|
347
|
+
end
|
348
|
+
|
321
349
|
def longitude=(long)
|
322
350
|
@longitude = Kamelopard.convert_coord(long)
|
323
351
|
end
|
@@ -327,10 +355,13 @@ module Kamelopard
|
|
327
355
|
end
|
328
356
|
|
329
357
|
def to_s
|
330
|
-
p @extrude
|
331
358
|
"Point (#{@longitude}, #{@latitude}, #{@altitude}, mode = #{@altitudeMode}, #{ @extrude == 1 ? 'extruded' : 'not extruded' })"
|
332
359
|
end
|
333
360
|
|
361
|
+
def extrude=(a)
|
362
|
+
@extrude = ((a and a != 0) ? 1 : 0)
|
363
|
+
end
|
364
|
+
|
334
365
|
def to_kml(elem = nil, short = false)
|
335
366
|
e = XML::Node.new 'Point'
|
336
367
|
super(e)
|
@@ -341,7 +372,7 @@ module Kamelopard
|
|
341
372
|
|
342
373
|
if not short then
|
343
374
|
c = XML::Node.new 'extrude'
|
344
|
-
c <<
|
375
|
+
c << @extrude.to_s
|
345
376
|
e << c
|
346
377
|
|
347
378
|
Kamelopard.add_altitudeMode(@altitudeMode, e)
|
@@ -499,7 +530,8 @@ module Kamelopard
|
|
499
530
|
|
500
531
|
# Abstract class corresponding to KML's AbstractView object
|
501
532
|
class AbstractView < Object
|
502
|
-
attr_accessor :timestamp, :timespan, :viewerOptions, :heading, :tilt,
|
533
|
+
attr_accessor :timestamp, :timespan, :viewerOptions, :heading, :tilt,
|
534
|
+
:roll, :range, :altitudeMode
|
503
535
|
attr_reader :className, :point
|
504
536
|
|
505
537
|
def initialize(className, point, options = {})
|
@@ -622,13 +654,14 @@ module Kamelopard
|
|
622
654
|
class Camera < AbstractView
|
623
655
|
def initialize(point = nil, options = {})
|
624
656
|
super('Camera', point, options)
|
657
|
+
@roll = 0 if @roll.nil?
|
625
658
|
end
|
626
659
|
|
627
660
|
def range
|
628
661
|
raise "The range element is part of LookAt objects, not Camera objects"
|
629
662
|
end
|
630
663
|
|
631
|
-
def range=
|
664
|
+
def range=(a)
|
632
665
|
# The range element doesn't exist in Camera objects
|
633
666
|
end
|
634
667
|
end
|
@@ -637,6 +670,37 @@ module Kamelopard
|
|
637
670
|
class LookAt < AbstractView
|
638
671
|
def initialize(point = nil, options = {})
|
639
672
|
super('LookAt', point, options)
|
673
|
+
@range = 0 if @range.nil?
|
674
|
+
end
|
675
|
+
|
676
|
+
def self.parse(la)
|
677
|
+
# TimePrimitive
|
678
|
+
#<!-- inherited from AbstractView element -->
|
679
|
+
#<TimePrimitive>...</TimePrimitive> <!-- gx:TimeSpan or gx:TimeStamp -->
|
680
|
+
# ViewerOptions
|
681
|
+
#<gx:ViewerOptions>
|
682
|
+
#<option> name=" " type="boolean"> <!-- name="streetview", "historicalimagery", "sunlight", or "groundnavigation" -->
|
683
|
+
#</option>
|
684
|
+
#</gx:ViewerOptions>
|
685
|
+
fields = %w[
|
686
|
+
longitude latitude altitude heading
|
687
|
+
tilt range altitudeMode
|
688
|
+
gx:TimeStamp gx:TimeSpan gx:ViewerOptions
|
689
|
+
]
|
690
|
+
b = Kamelopard.xml_to_hash(la, fields)
|
691
|
+
a = {}
|
692
|
+
b.each do |k, v|
|
693
|
+
if k == :altitudeMode
|
694
|
+
a[k] = v.to_s.to_sym
|
695
|
+
else
|
696
|
+
a[k] = v.to_s.to_f
|
697
|
+
end
|
698
|
+
end
|
699
|
+
return LookAt.new(
|
700
|
+
Point.new( a[:longitude], a[:latitude], a[:altitude], :altitudeMode => a[:altitudeMode]),
|
701
|
+
:heading => a[:heading],
|
702
|
+
:tilt => a[:tilt],
|
703
|
+
:range => a[:range])
|
640
704
|
end
|
641
705
|
|
642
706
|
def roll
|
@@ -791,8 +855,8 @@ module Kamelopard
|
|
791
855
|
end
|
792
856
|
|
793
857
|
def description=(a)
|
794
|
-
b = CGI.escapeHTML(a)
|
795
|
-
if b != a then
|
858
|
+
b = CGI.escapeHTML(a) if b.is_a? String
|
859
|
+
if (! a.is_a? XML::Node) and b != a then
|
796
860
|
@description = XML::Node.new_cdata a
|
797
861
|
else
|
798
862
|
@description = a
|
@@ -908,9 +972,10 @@ module Kamelopard
|
|
908
972
|
end
|
909
973
|
|
910
974
|
def styles_to_kml(elem)
|
975
|
+
# XXX Remove this
|
911
976
|
raise "done here" if elem.class == Array
|
912
977
|
@styles.each do |a|
|
913
|
-
a.to_kml(elem) unless a.attached?
|
978
|
+
a.to_kml(elem) # unless a.attached?
|
914
979
|
end
|
915
980
|
end
|
916
981
|
end
|
@@ -1003,7 +1068,7 @@ module Kamelopard
|
|
1003
1068
|
# this function, and tours for non-LG targets, work normally.
|
1004
1069
|
attr_accessor :master_mode
|
1005
1070
|
|
1006
|
-
def initialize(options = {})
|
1071
|
+
def initialize(name = '', options = {})
|
1007
1072
|
@tours = []
|
1008
1073
|
@folders = []
|
1009
1074
|
@vsr_actions = []
|
@@ -1087,18 +1152,44 @@ module Kamelopard
|
|
1087
1152
|
|
1088
1153
|
def get_kml_document
|
1089
1154
|
k = XML::Document.new
|
1155
|
+
|
1156
|
+
# # XXX Should this be add_namespace instead?
|
1157
|
+
# ns_arr = [
|
1158
|
+
# ['', 'http://www.opengis.net/kml/2.2'],
|
1159
|
+
# ['gx', 'http://www.google.com/kml/ext/2.2'],
|
1160
|
+
# ['kml', 'http://www.opengis.net/kml/2.2'],
|
1161
|
+
# ['atom', 'http://www.w3.org/2005/Atom'],
|
1162
|
+
# ['test', 'http://test.com']
|
1163
|
+
# ]
|
1164
|
+
# ns_arr.each do |a|
|
1165
|
+
# nm = 'xmlns'
|
1166
|
+
# nm = a[0] if a[0] != ''
|
1167
|
+
# k.context.register_namespace(nm, a[1])
|
1168
|
+
# end
|
1169
|
+
|
1090
1170
|
# XXX fix this
|
1091
1171
|
#k << XML::XMLDecl.default
|
1092
1172
|
k.root = XML::Node.new('kml')
|
1093
1173
|
r = k.root
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
r.attributes['xmlns'] = 'http://www.
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1174
|
+
# # XXX Should this be add_namespace instead?
|
1175
|
+
# r.attributes['xmlns'] = 'http://www.opengis.net/kml/2.2'
|
1176
|
+
# r.attributes['xmlns:gx'] = 'http://www.google.com/kml/ext/2.2'
|
1177
|
+
# r.attributes['xmlns:kml'] = 'http://www.opengis.net/kml/2.2'
|
1178
|
+
# r.attributes['xmlns:atom'] = 'http://www.w3.org/2005/Atom'
|
1179
|
+
ns_values = [
|
1180
|
+
[nil, 'http://www.opengis.net/kml/2.2'],
|
1181
|
+
['gx', 'http://www.google.com/kml/ext/2.2'],
|
1182
|
+
['kml', 'http://www.opengis.net/kml/2.2'],
|
1183
|
+
['atom', 'http://www.w3.org/2005/Atom'],
|
1184
|
+
]
|
1185
|
+
if @uses_xal
|
1186
|
+
ns_values << ['xal', "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"]
|
1187
|
+
end
|
1188
|
+
ns_values.each do |ns|
|
1189
|
+
n = XML::Namespace.new(r, ns[0], ns[1])
|
1190
|
+
r.namespaces.namespace = n if ns[0].nil?
|
1191
|
+
end
|
1192
|
+
|
1102
1193
|
r << self.to_kml
|
1103
1194
|
k
|
1104
1195
|
end
|
@@ -1152,6 +1243,15 @@ module Kamelopard
|
|
1152
1243
|
@document_index = a
|
1153
1244
|
end
|
1154
1245
|
|
1246
|
+
def delete_current_doc
|
1247
|
+
@documents.delete_at @document_index unless @document_index == -1
|
1248
|
+
if @documents.size > 0
|
1249
|
+
@document_index = @documents.size - 1
|
1250
|
+
else
|
1251
|
+
@document_index = -1
|
1252
|
+
end
|
1253
|
+
end
|
1254
|
+
|
1155
1255
|
def current_document
|
1156
1256
|
# Automatically create a Document if we don't already have one
|
1157
1257
|
if @documents.size <= 0
|
@@ -1196,6 +1296,7 @@ module Kamelopard
|
|
1196
1296
|
|
1197
1297
|
def initialize(color = nil, options = {})
|
1198
1298
|
super options
|
1299
|
+
@set_colorMode = false
|
1199
1300
|
@color = color unless color.nil?
|
1200
1301
|
end
|
1201
1302
|
|
@@ -1205,6 +1306,7 @@ module Kamelopard
|
|
1205
1306
|
|
1206
1307
|
def colorMode=(a)
|
1207
1308
|
validate_colorMode a
|
1309
|
+
@set_colorMode = true
|
1208
1310
|
@colorMode = a
|
1209
1311
|
end
|
1210
1312
|
|
@@ -1246,9 +1348,11 @@ module Kamelopard
|
|
1246
1348
|
e = XML::Node.new 'color'
|
1247
1349
|
e << @color
|
1248
1350
|
k << e
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1351
|
+
if @set_colorMode then
|
1352
|
+
e = XML::Node.new 'colorMode'
|
1353
|
+
e << @colorMode
|
1354
|
+
k << e
|
1355
|
+
end
|
1252
1356
|
k
|
1253
1357
|
end
|
1254
1358
|
end
|
@@ -1655,7 +1759,7 @@ module Kamelopard
|
|
1655
1759
|
|
1656
1760
|
def initialize(view = nil, options = {})
|
1657
1761
|
@duration = 0
|
1658
|
-
@mode =
|
1762
|
+
@mode = Kamelopard::DocumentHolder.instance.current_document.flyto_mode
|
1659
1763
|
super options
|
1660
1764
|
self.view= view unless view.nil?
|
1661
1765
|
end
|
@@ -1679,6 +1783,8 @@ module Kamelopard
|
|
1679
1783
|
def to_kml(elem = nil)
|
1680
1784
|
k = XML::Node.new 'gx:FlyTo'
|
1681
1785
|
super k
|
1786
|
+
|
1787
|
+
#k.namespaces.namespace = XML::Namespaces.new(k, 'gx', 'http://www.google.com/kml/ext/2.2')
|
1682
1788
|
Kamelopard.kml_array(k, [
|
1683
1789
|
[ @duration, 'gx:duration' ],
|
1684
1790
|
[ @mode, 'gx:flyToMode' ]
|
@@ -1777,6 +1883,8 @@ module Kamelopard
|
|
1777
1883
|
@duration = duration
|
1778
1884
|
end
|
1779
1885
|
|
1886
|
+
# Accepts an XML node representing a gx:Wait KML object, and turns it
|
1887
|
+
# into a Kamelopard::Wait object
|
1780
1888
|
def self.parse(x)
|
1781
1889
|
dur = nil
|
1782
1890
|
id = x.attributes['id'] if x.attributes? 'id'
|
data/lib/kamelopard/function.rb
CHANGED
@@ -8,7 +8,8 @@
|
|
8
8
|
module Kamelopard
|
9
9
|
|
10
10
|
# This function creates a hash, then uses that hash to create a point in a
|
11
|
-
# tour, using make_view_from() among other things.
|
11
|
+
# tour, using make_view_from() among other things. It will yield to a code
|
12
|
+
# block, if one is provided, as described below.
|
12
13
|
# Arguments:
|
13
14
|
# points: The number of points in the series
|
14
15
|
# hash: Values used to create the hash, which creates the point in the
|
@@ -20,18 +21,13 @@ module Kamelopard
|
|
20
21
|
# calculating, starting with 0, and the current value of the hash
|
21
22
|
# created for this point. "duration" represents the time in seconds
|
22
23
|
# spent flying from the last point to this one.
|
23
|
-
# callback
|
24
|
-
# This Proc object, if defined, will be called after the other hash
|
25
|
-
# keys have been calculated. It gets passed the number of the point,
|
26
|
-
# and the current value of the hash for this point. It can modify and
|
27
|
-
# return that hash as needed.
|
28
24
|
# callback_value
|
29
25
|
# A placeholder the callback function can use. It can set it when
|
30
26
|
# it's called one time, and see that value when called the next time.
|
31
27
|
# pause
|
32
|
-
# The amount of time to pause after flying to
|
28
|
+
# The amount of time to pause after flying to each point, or nil for no pause
|
33
29
|
# show_placemarks
|
34
|
-
# If set, a placemark object will be created at
|
30
|
+
# If set, a placemark object will be created at each point
|
35
31
|
# no_flyto
|
36
32
|
# If set, on flyto objects will be created
|
37
33
|
# multidim
|
@@ -75,13 +71,13 @@ module Kamelopard
|
|
75
71
|
|
76
72
|
callback_value = nil
|
77
73
|
i = 0
|
78
|
-
while (i
|
74
|
+
while (i < points)
|
79
75
|
p = i.to_f / points.to_f
|
80
76
|
hash = {}
|
81
77
|
[ :latitude, :longitude, :altitude, :heading,
|
82
78
|
:tilt, :altitudeMode, :extrude, :when,
|
83
79
|
:roll, :range, :pause, :begin, :end, :show_placemarks,
|
84
|
-
:no_flyto
|
80
|
+
:no_flyto
|
85
81
|
].each do |k|
|
86
82
|
if options.has_key? k then
|
87
83
|
hash[k] = val(options[k], i, p)
|
data/lib/kamelopard/geocode.rb
CHANGED
data/lib/kamelopard/helpers.rb
CHANGED
@@ -12,6 +12,13 @@
|
|
12
12
|
def set_flyto_mode_to(mode)
|
13
13
|
Kamelopard::DocumentHolder.instance.current_document.flyto_mode = mode
|
14
14
|
end
|
15
|
+
|
16
|
+
# Clears out the document_holder
|
17
|
+
def clear_documents
|
18
|
+
dh = get_doc_holder
|
19
|
+
dh.delete_current_doc while dh.documents.size > 0
|
20
|
+
end
|
21
|
+
|
15
22
|
|
16
23
|
# Shows or hides the popup balloon for Placemark and ScreenOverlay objects.
|
17
24
|
# Arguments are the object; 0 or 1 to hide or show the balloon, respectively;
|
@@ -153,45 +160,88 @@
|
|
153
160
|
l.range += dist
|
154
161
|
Kamelopard::FlyTo.new(l, nil, dur, mode)
|
155
162
|
end
|
163
|
+
|
164
|
+
# Translates a heading into something between 0 and 360
|
165
|
+
def convert_heading(heading)
|
166
|
+
if heading > 360 then
|
167
|
+
step = -360
|
168
|
+
else
|
169
|
+
step = 360
|
170
|
+
end
|
171
|
+
while heading < 0 or heading > 360 do
|
172
|
+
heading = heading + step
|
173
|
+
end
|
174
|
+
heading
|
175
|
+
end
|
156
176
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
step = num / den
|
176
|
-
step = 1 if step < 1
|
177
|
-
step = step * -1 if startHeading > endHeading
|
178
|
-
|
179
|
-
lastval = startHeading
|
180
|
-
mode = :bounce
|
181
|
-
startHeading.step(endHeading, step) do |theta|
|
182
|
-
lastval = theta
|
183
|
-
fly_to Kamelopard::LookAt.new(center, :heading => theta, :tilt => tilt, :range => range, :altitudeMode => am), :duration => 2, :mode => mode
|
184
|
-
mode = :smooth
|
185
|
-
end
|
186
|
-
if lastval != endHeading then
|
187
|
-
fly_to Kamelopard::LookAt.new(center, :heading => endHeading, :tilt => tilt, :range => range, :altitudeMode => am), :duration => 2, :mode => :smooth
|
188
|
-
end
|
189
|
-
end
|
177
|
+
# Creates a list of FlyTo elements to orbit and look at a given point (center),
|
178
|
+
# at a given range (in meters), starting and ending at given angles (in
|
179
|
+
# degrees) from the center, where 0 and 360 (and -360, and 720, and -980, etc.)
|
180
|
+
# are north. To orbit multiple times, add or subtract 360 from the
|
181
|
+
# endHeading.
|
182
|
+
# The tilt argument matches the KML LookAt tilt argument
|
183
|
+
# already_there, if true, means we've already flown to the initial point
|
184
|
+
# The options hash can contain:
|
185
|
+
# :duration The total duration of the orbit. Defaults to 0, which means it will take 2 seconds per step
|
186
|
+
# :step How much to change the heading for each flyto. Defaults to some strange value >= 5
|
187
|
+
# :already_there
|
188
|
+
# Default false. Indicates that we've already flown to the initial position
|
189
|
+
def orbit(center, range = 100, tilt = 90, startHeading = 0, endHeading = 360, options = {})
|
190
|
+
duration = options.has_key?(:duration) ? options[:duration] : 0
|
191
|
+
step = options.has_key?(:step) ? options[:step] : nil
|
192
|
+
already_there = options.has_key?(:already_there) ? options[:already_there] : false
|
193
|
+
|
194
|
+
am = center.altitudeMode
|
190
195
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
196
|
+
if not step.nil? then
|
197
|
+
if (endHeading - startHeading > 0 and step < 0) or (endHeading - startHeading < 0 and step > 0) then
|
198
|
+
raise "Given start = #{startHeading}, end = #{endHeading}, and step = #{step}, this will be an infinite loop"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# We want at least 5 points (arbitrarily chosen value), plus at least 5 for
|
203
|
+
# each full revolution
|
204
|
+
|
205
|
+
# When I tried this all in one step, ruby told me 360 / 10 = 1805. I'm sure
|
206
|
+
# there's some reason why this is a feature and not a bug, but I'd rather
|
207
|
+
# not look it up right now.
|
208
|
+
dur = 2
|
209
|
+
if step.nil? then
|
210
|
+
num = (endHeading - startHeading).abs
|
211
|
+
num_steps = ((endHeading - startHeading) / 360.0).to_i.abs * 5 + 5
|
212
|
+
step = num / num_steps
|
213
|
+
step = 1 if step < 1
|
214
|
+
step = step * -1 if startHeading > endHeading
|
215
|
+
if already_there
|
216
|
+
num_steps = num_steps - 1
|
217
|
+
startHeading = startHeading + step
|
218
|
+
end
|
219
|
+
if duration != 0
|
220
|
+
dur = duration.to_f / num_steps
|
221
|
+
end
|
222
|
+
else
|
223
|
+
dur = duration * 1.0 / ((endHeading - startHeading) * 1.0 / step) if duration != 0
|
224
|
+
startHeading = startHeading + step if already_there
|
225
|
+
end
|
226
|
+
|
227
|
+
lastval = startHeading
|
228
|
+
mode = :bounce
|
229
|
+
mode = :smooth if already_there
|
230
|
+
startHeading.step(endHeading, step) do |theta|
|
231
|
+
lastval = theta
|
232
|
+
heading = convert_heading theta
|
233
|
+
fly_to Kamelopard::LookAt.new(center, :heading => heading, :tilt => tilt, :range => range, :altitudeMode => am), :duration => dur, :mode => mode
|
234
|
+
mode = :smooth
|
235
|
+
end
|
236
|
+
if lastval != endHeading then
|
237
|
+
fly_to Kamelopard::LookAt.new(center, :heading => convert_heading(endHeading), :tilt => tilt, :range => range, :altitudeMode => am), :duration => dur, :mode => :smooth
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Adds a SoundCue object.
|
242
|
+
def sound_cue(href, ds = nil)
|
243
|
+
Kamelopard::SoundCue.new href, ds
|
244
|
+
end
|
195
245
|
|
196
246
|
# XXX This implementation of orbit is trying to do things the hard way, but the code might be useful for other situations where the hard way is the only possible one
|
197
247
|
# def orbit(center, range = 100, startHeading = 0, endHeading = 360)
|
@@ -505,6 +555,7 @@
|
|
505
555
|
|
506
556
|
# Pulls the Placemarks from the KML document d and yields each in turn to the caller
|
507
557
|
# d = an XML::Document containing KML
|
558
|
+
# XXX This currently expects Placemarks to contain LookAt or Camera objects. It should do something useful with placemarks based on Points, or other objects
|
508
559
|
def each_placemark(d)
|
509
560
|
d.find('//kml:Placemark').each do |p|
|
510
561
|
all_values = {}
|
@@ -616,7 +667,9 @@
|
|
616
667
|
|
617
668
|
# Generates a series of points in a path that will simulate Earth's FlyTo in
|
618
669
|
# bounce mode, from one view to another. Note that the view objects must be
|
619
|
-
# the same time: either LookAt, or Camera
|
670
|
+
# the same time: either LookAt, or Camera. Options include :no_flyto and
|
671
|
+
# :show_placemarks, and match make_function_path's meanings for those
|
672
|
+
# options
|
620
673
|
#--
|
621
674
|
# XXX Fix the limitation that the views must be the same type
|
622
675
|
# XXX Make it slow down a bit toward the end of the run
|
@@ -672,16 +725,20 @@
|
|
672
725
|
# Returns the great circle distance between two points
|
673
726
|
def great_circle_distance(a, b)
|
674
727
|
# Stolen from http://rosettacode.org/wiki/Haversine_formula#Ruby
|
675
|
-
include Math
|
676
728
|
|
677
729
|
def deg2rad(a)
|
678
|
-
a * PI / 180
|
730
|
+
a * Math::PI / 180
|
679
731
|
end
|
680
732
|
|
681
733
|
radius = 6371 # rough radius of the Earth, in kilometers
|
682
734
|
lat1, long1 = [Math::PI * a.latitude / 180.0, Math::PI * a.longitude / 180.0]
|
683
735
|
lat2, long2 = [Math::PI * b.latitude / 180.0, Math::PI * b.longitude / 180.0]
|
684
|
-
d = 2 * radius *
|
736
|
+
d = 2 * radius *
|
737
|
+
Math.asin( Math.sqrt(
|
738
|
+
Math.sin((lat2-lat1)/2)**2 +
|
739
|
+
Math.cos(lat1) * Math.cos(lat2) *
|
740
|
+
Math.sin((long2 - long1)/2)**2
|
741
|
+
))
|
685
742
|
|
686
743
|
return d
|
687
744
|
end
|
data/lib/kamelopard/multicam.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'bundler/setup'
|
1
2
|
require 'matrix'
|
2
3
|
|
3
4
|
module Kamelopard
|
@@ -84,7 +85,7 @@ module Kamelopard
|
|
84
85
|
# the camera vector.
|
85
86
|
transformed_up = rot_z(heading) * rot_x(tilt) * Vector[0,1,0]
|
86
87
|
if cross_product(up_vec, transformed_up).r == 0
|
87
|
-
negate =
|
88
|
+
negate = ! (same_quadrant(up_vec, transformed_up))
|
88
89
|
else
|
89
90
|
negate = same_quadrant(vec, cross_product(up_vec, transformed_up))
|
90
91
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Kamelopard
|
2
|
+
module Regionate
|
3
|
+
#require 'ai4r'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'hierclust'
|
6
|
+
|
7
|
+
class Hierclust::Point
|
8
|
+
attr_accessor :placemark
|
9
|
+
def <=>(a)
|
10
|
+
raise "Something called this!"
|
11
|
+
end
|
12
|
+
|
13
|
+
def distance_to(b)
|
14
|
+
if self.is_a? Hierclust::Point then
|
15
|
+
pt1 = point(self.x, self.y)
|
16
|
+
else
|
17
|
+
pt1 = self.placemark.geometry
|
18
|
+
end
|
19
|
+
if b.is_a? Hierclust::Point then
|
20
|
+
pt2 = point(b.x, b.y)
|
21
|
+
else
|
22
|
+
pt2 = b.placemark.geometry
|
23
|
+
end
|
24
|
+
great_circle_distance(pt1, pt2)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Regionate a bunch of placemarks. Add placemarks representing the centers
|
29
|
+
# of the discovered clusters to a folder if one is provided.
|
30
|
+
# Options:
|
31
|
+
# :folder => The folder to add placemarks for each region to. Defaults to
|
32
|
+
# nil, in which case no placemarks will be added
|
33
|
+
# :minlod => minLod value for each region, defaults to 12000
|
34
|
+
# :maxlod => minLod value for each region, defaults to -1 # :dist => minimum distance in degrees of lat/long between cluster centers
|
35
|
+
#def self.regionate(placemarks, count, options = {})
|
36
|
+
def self.regionate(placemarks, dist, options = {})
|
37
|
+
#include Ai4r::Data
|
38
|
+
#include Ai4r::Clusterers
|
39
|
+
|
40
|
+
options[:minlod] = 12000 unless options.has_key? :minlod and not options[:minlod].nil?
|
41
|
+
options[:maxlod] = -1 unless options.has_key? :maxlod and not options[:maxlod].nil?
|
42
|
+
hierpoints = []
|
43
|
+
|
44
|
+
placemarks.each do |g|
|
45
|
+
h = Hierclust::Point.new(g.longitude.to_f, g.latitude.to_f)
|
46
|
+
h.placemark = g
|
47
|
+
hierpoints << h
|
48
|
+
end
|
49
|
+
clusterer = Hierclust::Clusterer.new(hierpoints, dist)
|
50
|
+
|
51
|
+
centers = []
|
52
|
+
pmarks = []
|
53
|
+
regions = []
|
54
|
+
|
55
|
+
clusterer.clusters.each do |c|
|
56
|
+
Kamelopard.log(:debug, 'regionate', "Here's a cluster: #{c.points}")
|
57
|
+
|
58
|
+
r = Kamelopard::Region.new(
|
59
|
+
# Do something based on ... dist, perhaps, when radius is 0
|
60
|
+
:latlonaltbox => Kamelopard::LatLonBox.new(
|
61
|
+
# n, s, e, w
|
62
|
+
c.y + c.radius,
|
63
|
+
c.y - c.radius,
|
64
|
+
c.x + c.radius,
|
65
|
+
c.x - c.radius,
|
66
|
+
0
|
67
|
+
),
|
68
|
+
:lod => Kamelopard::Lod.new(options[:minlod], options[:maxlod], 0, 0)
|
69
|
+
# - :lod => Kamelopard::Lod.new(-1, options[:minlod], 0, 0)
|
70
|
+
)
|
71
|
+
regions << r
|
72
|
+
|
73
|
+
# Find placemarks in this cluster, and add region
|
74
|
+
c.points.each do |pt|
|
75
|
+
Kamelopard.log(:debug, 'regionate', "Found placemark #{pt.placemark.name} in the cluster")
|
76
|
+
pt.placemark.region = r
|
77
|
+
end
|
78
|
+
|
79
|
+
pl = placemark "#{c.size}-element cluster at #{c.y}, #{c.x}", :geometry => point(c.x, c.y, 0)
|
80
|
+
c_r = Kamelopard::Region.new(
|
81
|
+
:latlonaltbox => Kamelopard::LatLonBox.new(
|
82
|
+
# n, s, e, w
|
83
|
+
c.y + c.radius,
|
84
|
+
c.y - c.radius,
|
85
|
+
c.x + c.radius,
|
86
|
+
c.x - c.radius,
|
87
|
+
0
|
88
|
+
),
|
89
|
+
:lod => Kamelopard::Lod.new(-1, options[:minlod], 0, 0)
|
90
|
+
)
|
91
|
+
pl.region = c_r
|
92
|
+
options[:folder] << pl unless options[:folder].nil?
|
93
|
+
pmarks << pl
|
94
|
+
end
|
95
|
+
|
96
|
+
[ clusterer.clusters, centers, pmarks ]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/kamelopard/spline.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# vim:ts=4:sw=4:et:smartindent:nowrap
|
2
|
+
require 'bundler/setup'
|
2
3
|
require 'matrix'
|
3
4
|
|
4
5
|
# Basic support for splines
|
@@ -82,7 +83,7 @@ module Kamelopard
|
|
82
83
|
]
|
83
84
|
|
84
85
|
p = Matrix[[1, u, u**2, u**3]] * h * cps
|
85
|
-
return p
|
86
|
+
return p.row(0)
|
86
87
|
end
|
87
88
|
end ## End of SplineFunction class
|
88
89
|
end ## End of Function module
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kamelopard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-12-12 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: Various classes and functions used to ease development of KML files,
|
16
16
|
in particular for development of Google Earth tours
|
@@ -23,6 +23,7 @@ extra_rdoc_files: []
|
|
23
23
|
files:
|
24
24
|
- lib/kamelopard/multicam.rb
|
25
25
|
- lib/kamelopard/multicamify.rb
|
26
|
+
- lib/kamelopard/regionate.rb
|
26
27
|
- lib/kamelopard/geocode.rb
|
27
28
|
- lib/kamelopard/classes.rb
|
28
29
|
- lib/kamelopard/helpers.rb
|
@@ -50,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
51
|
version: '0'
|
51
52
|
requirements: []
|
52
53
|
rubyforge_project:
|
53
|
-
rubygems_version: 1.8.
|
54
|
+
rubygems_version: 1.8.23
|
54
55
|
signing_key:
|
55
56
|
specification_version: 3
|
56
57
|
summary: Tools for building KML
|