rgeo-kml 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 06227b992bdd5a2c5169650431585db1648c8b04
4
+ data.tar.gz: df4e6c62b71585076ddafef8721e85f770b4f871
5
+ SHA512:
6
+ metadata.gz: 4fb3bb15dd469492f7dda89d9a6141441531e9afa19f3166ba40d253a690ad1b56df2fb212d11276cf3a7979bd1fce0831653bd9f16d42ae1225e0e3a4804c3d
7
+ data.tar.gz: 786a6e34ea432784590336be029e4e466ea47ad8d3644701ce2693155953edfcd2a1afb4daf6581525ec9f4577d70fe097230377db22f71fb2f26b6e0533bfba
data/Version ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,7 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Kml implementation for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ require 'rgeo/kml'
@@ -0,0 +1,39 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Kml implementation for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ # Dependencies
8
+ require 'rgeo'
9
+
10
+ # RGeo is a spatial data library for Ruby, provided by the "rgeo" gem.
11
+ #
12
+ # The optional RGeo::GeoJSON module provides a set of tools for GeoJSON
13
+ # encoding and decoding.
14
+
15
+ module RGeo
16
+
17
+
18
+ # This is a namespace for a set of tools that provide KML encoding.
19
+ # See https://developers.google.com/kml/documentation/ for more information about this specification.
20
+
21
+ module Kml
22
+ end
23
+
24
+
25
+ end
26
+
27
+
28
+ # Implementation files
29
+ require 'rgeo/kml/version'
30
+ require 'rgeo/kml/entities'
31
+ require 'rgeo/kml/coder'
32
+ require 'rgeo/kml/interface'
33
+ require 'rgeo/kml/kml_stream_listener'
34
+ require 'rgeo/kml/coordinates_builder'
35
+ require 'rgeo/kml/point_builder'
36
+ require 'rgeo/kml/line_string_builder'
37
+ require 'rgeo/kml/linear_ring_builder'
38
+ require 'rgeo/kml/polygon_builder'
39
+ require 'rgeo/kml/multi_geometry_builder'
@@ -0,0 +1,222 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Kml encoder object
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ require "rexml/document"
8
+ require 'rexml/parsers/pullparser'
9
+
10
+ module RGeo
11
+
12
+ module Kml
13
+
14
+
15
+ # This object encapsulates encoding and decoding settings (principally
16
+ # the RGeo::Feature::Factory and the RGeo::Kml::EntityFactory to
17
+ # be used) so that you can encode and decode without specifying those
18
+ # settings every time.
19
+
20
+ class Coder
21
+
22
+ ELEMENT_MAP = {
23
+ 'Point' => "point",
24
+ 'LineString' => "line_string",
25
+ 'LinearRing' => "linear_ring",
26
+ 'Polygon' => "polygon",
27
+ 'MultiGeometry' => "geometry_collection"
28
+ }
29
+
30
+ @@kml_available = nil
31
+ @@activesupport_available = nil
32
+
33
+
34
+ # Create a new coder settings object. The geo factory is passed as
35
+ # a required argument.
36
+ #
37
+ # Options include:
38
+ #
39
+ # [<tt>:geo_factory</tt>]
40
+ # Specifies the geo factory to use to create geometry objects.
41
+ # Defaults to the preferred cartesian factory.
42
+ # [<tt>:entity_factory</tt>]
43
+ # Specifies an entity factory, which lets you override the types
44
+ # of Kml entities that are created. It defaults to the default
45
+ # RGeo::Kml::EntityFactory, which generates objects of type
46
+ # RGeo::Kml::Feature or RGeo::Kml::FeatureCollection.
47
+ # See RGeo::Kml::EntityFactory for more information.
48
+
49
+ def initialize(opts_={})
50
+ @geo_factory = opts_[:geo_factory] || ::RGeo::Cartesian.preferred_factory
51
+ @entity_factory = opts_[:entity_factory] || EntityFactory.instance
52
+ @buffer = ''
53
+
54
+ @num_coordinates = 2
55
+ @num_coordinates += 1 if @geo_factory.property(:has_z_coordinate)
56
+ @num_coordinates += 1 if @geo_factory.property(:has_m_coordinate)
57
+ end
58
+
59
+
60
+ # Encode the given object as Kml. The object may be one of the
61
+ # geometry objects specified in RGeo::Feature, or an appropriate
62
+ # Kml wrapper entity supported by this coder's entity factory.
63
+ #
64
+ # This method returns a KML object (xml with Placemark).
65
+ #
66
+ # Returns nil if nil is passed in as the object.
67
+
68
+ def encode(object_)
69
+ if @entity_factory.is_feature_collection?(object_)
70
+ {
71
+ 'type' => 'FeatureCollection',
72
+ 'features' => @entity_factory.map_feature_collection(object_){ |f_| _encode_feature(f_) },
73
+ }
74
+ elsif @entity_factory.is_feature?(object_)
75
+ _encode_feature(object_)
76
+ elsif object_.nil?
77
+ nil
78
+ else
79
+ _encode_geometry(object_)
80
+ end
81
+ end
82
+
83
+
84
+ # Returns the RGeo::Feature::Factory used to generate geometry objects.
85
+
86
+ def geo_factory
87
+ @geo_factory
88
+ end
89
+
90
+
91
+ # Returns the RGeo::Kml::EntityFactory used to generate Kml
92
+ # wrapper entities.
93
+
94
+ def entity_factory
95
+ @entity_factory
96
+ end
97
+
98
+
99
+ def _encode_feature(object_) # :nodoc:
100
+ id_ = @entity_factory.get_feature_id(object_)
101
+ kml_ = '<?xml version="1.0" encoding="UTF-8"?>\n'
102
+ kml_ += '<kml xmlns="http://www.opengis.net/kml/2.2">\n'
103
+ kml_ += "<Document>"
104
+ kml_ += "<name>Kml File</name>"
105
+ kml_ += "<Placemark id="#{id_}">\n"
106
+ kml_ += _encode_geometry(@entity_factory.get_feature_geometry(object_))
107
+ kml_ += "</Placemark>\n"
108
+ kml_ += "</Document>"
109
+ kml_ += "</kml>"
110
+
111
+ # 'properties' => @entity_factory.get_feature_properties(object_).dup,
112
+ # id_ = @entity_factory.get_feature_id(object_)
113
+ # json_['id'] = id_ if id_
114
+ kml_
115
+ end
116
+
117
+
118
+ def _encode_geometry(object_, point_encoder_=nil) # :nodoc:
119
+ unless point_encoder_
120
+ if object_.factory.property(:has_z_coordinate)
121
+ if object_.factory.property(:has_m_coordinate)
122
+ point_encoder_ = ::Proc.new{ |p_| [p_.x, p_.y, p_.z, p_.m].join(",") }
123
+ else
124
+ point_encoder_ = ::Proc.new{ |p_| [p_.x, p_.y, p_.z].join(",") }
125
+ end
126
+ else
127
+ if object_.factory.property(:has_m_coordinate)
128
+ point_encoder_ = ::Proc.new{ |p_| [p_.x, p_.y, p_.m].join(",") }
129
+ else
130
+ point_encoder_ = ::Proc.new{ |p_| [p_.x, p_.y].join(",") }
131
+ end
132
+ end
133
+ end
134
+ case object_
135
+ when ::RGeo::Feature::Point
136
+ result = "<Point>\n"
137
+ #result += options[:geom_data] if options[:geom_data]
138
+ result += "<coordinates>" + point_encoder_.call(object_)
139
+ result += "</coordinates>\n"
140
+ result += "</Point>\n"
141
+ when ::RGeo::Feature::LineString
142
+ result = "<LineString>\n"
143
+ #result += options[:geom_data] if options[:geom_data]
144
+ result += "<coordinates>\n"
145
+ result += object_.points.map(&point_encoder_).join("\n")
146
+ result += "\n</coordinates>\n"
147
+ result += "</LineString>\n"
148
+ when ::RGeo::Feature::Polygon
149
+ result = "<Polygon>\n"
150
+ #result += options[:geom_data] if options[:geom_data]
151
+ result += "<outerBoundaryIs><LinearRing><coordinates>\n"
152
+ result += object_.exterior_ring.points.map(&point_encoder_).join("\n")
153
+ result += "\n</coordinates></LinearRing></outerBoundaryIs>\n"
154
+ object_.interior_rings.each do |interior_ring|
155
+ result += "<innerBoundaryIs><LinearRing><coordinates>\n"
156
+ result += interior_ring.points.map(&point_encoder_).join("\n")
157
+ result += "\n</coordinates></LinearRing></innerBoundaryIs>\n"
158
+ end
159
+ result += "</Polygon>\n"
160
+ when ::RGeo::Feature::MultiPoint
161
+ result = "<MultiGeometry>\n"
162
+ object_.each do |geometry|
163
+ result += "<Point>\n<coordinates>"
164
+ #options[:id_attr] = "" #the subgeometries do not have an ID
165
+ result += point_encoder_.call(geometry)
166
+ result += "</coordinates>\n</Point>\n"
167
+ end
168
+ result += "</MultiGeometry>\n"
169
+ when ::RGeo::Feature::MultiLineString
170
+ result = "<MultiGeometry>\n"
171
+ object_.each do |geometry|
172
+ #options[:id_attr] = "" #the subgeometries do not have an ID
173
+ result += "<LineString>\n"
174
+ #result += options[:geom_data] if options[:geom_data]
175
+ result += "<coordinates>\n"
176
+ result += geometry.points.map(&point_encoder_).join("\n")
177
+ result += "\n</coordinates>\n"
178
+ result += "</LineString>\n"
179
+ end
180
+ result += "</MultiGeometry>\n"
181
+ when ::RGeo::Feature::MultiPolygon
182
+ result = "<MultiGeometry>\n"
183
+ object_.each do |geometry|
184
+ #options[:id_attr] = "" #the subgeometries do not have an ID
185
+ result += "<Polygon>\n"
186
+ #result += options[:geom_data] if options[:geom_data]
187
+ result += "<outerBoundaryIs><LinearRing><coordinates>\n"
188
+ result += geometry.exterior_ring.points.map(&point_encoder_).join("\n")
189
+ result += "\n</coordinates></LinearRing></outerBoundaryIs>\n"
190
+ geometry.interior_rings.each do |interior_ring|
191
+ result += "<innerBoundaryIs><LinearRing><coordinates>\n"
192
+ result += interior_ring.points.map(&point_encoder_).join("\n")
193
+ result += "\n</coordinates></LinearRing></innerBoundaryIs>\n"
194
+ end
195
+ result += "</Polygon>\n"
196
+ end
197
+ result += "</MultiGeometry>\n"
198
+ when ::RGeo::Feature::GeometryCollection
199
+ result = "<MultiGeometry>\n"
200
+ #options[:id_attr] = "" #the subgeometries do not have an ID
201
+ result += object_.map{ |geom_| _encode_geometry(geom_, point_encoder_) }.join("")
202
+ result += "</MultiGeometry>\n"
203
+ else
204
+ nil
205
+ end
206
+ end
207
+
208
+
209
+ # Decode an object from Kml. The input may a
210
+ # String, or an IO object from which to read the KML string.
211
+ # If an error occurs, nil is returned.
212
+ def decode(input_)
213
+ @kml_stream_listener = KmlStreamListener.new(geo_factory)
214
+ @kml_stream_listener.parse(input_)
215
+ @kml_stream_listener.result
216
+ end
217
+
218
+ end
219
+
220
+ end
221
+
222
+ end
@@ -0,0 +1,37 @@
1
+ module RGeo
2
+ module Kml
3
+
4
+ class CoordinatesBuilder
5
+ attr_reader :geo_factory, :parent, :points
6
+ attr_accessor :text
7
+
8
+ def initialize( geo_factory, parent)
9
+ @geo_factory = geo_factory
10
+ @parent = parent
11
+ @points = []
12
+ end
13
+
14
+ def build
15
+ @text.gsub(/\n/, ' ').strip.split(/\s+/).each do |coord|
16
+ x, y, z = coord.split(',')
17
+ if x.nil? || y.nil?
18
+ fail StandardError, 'Coordinates must have at least x and y elements'
19
+ end
20
+ if z.nil?
21
+ @points << @geo_factory.point(x, y)
22
+ else
23
+ @points << @geo_factory.point(x, y, z)
24
+ end
25
+
26
+ @points
27
+ end
28
+
29
+ rescue Exception => e
30
+ puts "Exception #{e.message} \n #{e.backtrace}"
31
+ raise StandardError, 'Error parsing coordinates: check your kml for errors'
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,275 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Kml standard entities
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+
8
+ module RGeo
9
+
10
+ module Kml
11
+
12
+
13
+ # This is a Kml wrapper entity that corresponds to the Kml
14
+ # "Feature" type. It is an immutable type.
15
+ #
16
+ # This is the default implementation that is generated by
17
+ # RGeo::Kml::EntityFactory. You may replace this implementation
18
+ # by writing your own entity factory. Note that an alternate Feature
19
+ # implementation need not subclass or even duck-type this class.
20
+ # the entity factory mediates all interaction between the Kml
21
+ # engine and features.
22
+
23
+ class Feature
24
+
25
+
26
+ # Create a feature wrapping the given geometry, with the given ID
27
+ # and properties.
28
+
29
+ def initialize(geometry_, id_=nil, properties_={})
30
+ @geometry = geometry_
31
+ @id = id_
32
+ @properties = {}
33
+ properties_.each do |k_, v_|
34
+ @properties[k_.to_s] = v_
35
+ end
36
+ end
37
+
38
+
39
+ def inspect # :nodoc:
40
+ "#<#{self.class}:0x#{object_id.to_s(16)} id=#{@id.inspect} geom=#{@geometry ? @geometry.as_text.inspect : 'nil'}>"
41
+ end
42
+
43
+ def to_s # :nodoc:
44
+ inspect
45
+ end
46
+
47
+ def hash # :nodoc:
48
+ @geometry.hash + @id.hash + @properties.hash
49
+ end
50
+
51
+
52
+ # Two features are equal if their geometries, IDs, and properties
53
+ # are all equal.
54
+ # This method uses the eql? method to test geometry equality, which
55
+ # may behave differently than the == operator.
56
+
57
+ def eql?(rhs_)
58
+ rhs_.kind_of?(Feature) && @geometry.eql?(rhs_.geometry) && @id.eql?(rhs_.feature_id) && @properties.eql?(rhs_.instance_variable_get(:@properties))
59
+ end
60
+
61
+
62
+ # Two features are equal if their geometries, IDs, and properties
63
+ # are all equal.
64
+ # This method uses the == operator to test geometry equality, which
65
+ # may behave differently than the eql? method.
66
+
67
+ def ==(rhs_)
68
+ rhs_.kind_of?(Feature) && @geometry == rhs_.geometry && @id == rhs_.feature_id && @properties == rhs_.instance_variable_get(:@properties)
69
+ end
70
+
71
+
72
+ # Returns the geometry contained in this feature, which may be nil.
73
+
74
+ def geometry
75
+ @geometry
76
+ end
77
+
78
+
79
+ # Returns the ID for this feature, which may be nil.
80
+
81
+ def feature_id
82
+ @id
83
+ end
84
+
85
+
86
+ # Returns a copy of the properties for this feature.
87
+
88
+ def properties
89
+ @properties.dup
90
+ end
91
+
92
+
93
+ # Gets the value of the given named property.
94
+ # Returns nil if the given property is not found.
95
+
96
+ def property(key_)
97
+ @properties[key_.to_s]
98
+ end
99
+ alias_method :[], :property
100
+
101
+
102
+ # Gets an array of the known property keys in this feature.
103
+
104
+ def keys
105
+ @properties.keys
106
+ end
107
+
108
+
109
+ end
110
+
111
+
112
+ # This is a Kml wrapper entity that corresponds to the Kml
113
+ # "FeatureCollection" type. It is an immutable type.
114
+ #
115
+ # This is the default implementation that is generated by
116
+ # RGeo::Kml::EntityFactory. You may replace this implementation
117
+ # by writing your own entity factory. Note that an alternate
118
+ # FeatureCollection implementation need not subclass or even
119
+ # duck-type this class. The entity factory mediates all interaction
120
+ # between the Kml engine and feature collections.
121
+
122
+ class FeatureCollection
123
+
124
+ include ::Enumerable
125
+
126
+
127
+ # Create a new FeatureCollection with the given features, which must
128
+ # be provided as an Enumerable.
129
+
130
+ def initialize(features_=[])
131
+ @features = []
132
+ features_.each{ |f_| @features << f_ if f_.kind_of?(Feature) }
133
+ end
134
+
135
+
136
+ def inspect # :nodoc:
137
+ "#<#{self.class}:0x#{object_id.to_s(16)}>"
138
+ end
139
+
140
+ def to_s # :nodoc:
141
+ inspect
142
+ end
143
+
144
+ def hash # :nodoc:
145
+ @features.hash
146
+ end
147
+
148
+
149
+ # Two feature collections are equal if they contain the same
150
+ # features in the same order.
151
+ # This methods uses the eql? method to test geometry equality, which
152
+ # may behave differently than the == operator.
153
+
154
+ def eql?(rhs_)
155
+ rhs_.kind_of?(FeatureCollection) && @features.eql?(rhs_.instance_variable_get(:@features))
156
+ end
157
+
158
+
159
+ # Two feature collections are equal if they contain the same
160
+ # features in the same order.
161
+ # This methods uses the == operator to test geometry equality, which
162
+ # may behave differently than the eql? method.
163
+
164
+ def ==(rhs_)
165
+ rhs_.kind_of?(FeatureCollection) && @features == rhs_.instance_variable_get(:@features)
166
+ end
167
+
168
+
169
+ # Iterates or returns an iterator for the features.
170
+
171
+ def each(&block_)
172
+ @features.each(&block_)
173
+ end
174
+
175
+
176
+ # Returns the number of features contained in this collection.
177
+
178
+ def size
179
+ @features.size
180
+ end
181
+
182
+
183
+ # Access a feature by index.
184
+
185
+ def [](index_)
186
+ @features[index_]
187
+ end
188
+
189
+
190
+ end
191
+
192
+
193
+ # This is the default entity factory. It creates objects of type
194
+ # RGeo::Kml::Feature and RGeo::Kml::FeatureCollection.
195
+ # You may create your own entity factory by duck-typing this class.
196
+
197
+ class EntityFactory
198
+
199
+
200
+ # Create and return a new feature, given geometry, ID, and
201
+ # properties hash. Note that, per the Kml spec, geometry and/or
202
+ # properties may be nil.
203
+
204
+ def feature(geometry_, id_=nil, properties_=nil)
205
+ Feature.new(geometry_, id_, properties_ || {})
206
+ end
207
+
208
+
209
+ # Create and return a new feature collection, given an enumerable
210
+ # of feature objects.
211
+
212
+ def feature_collection(features_=[])
213
+ FeatureCollection.new(features_)
214
+ end
215
+
216
+
217
+ # Returns true if the given object is a feature created by this
218
+ # entity factory.
219
+
220
+ def is_feature?(object_)
221
+ object_.kind_of?(Feature)
222
+ end
223
+
224
+
225
+ # Returns true if the given object is a feature collection created
226
+ # by this entity factory.
227
+
228
+ def is_feature_collection?(object_)
229
+ object_.kind_of?(FeatureCollection)
230
+ end
231
+
232
+
233
+ # Run Enumerable#map on the features contained in the given feature
234
+ # collection.
235
+
236
+ def map_feature_collection(object_, &block_)
237
+ object_.map(&block_)
238
+ end
239
+
240
+
241
+ # Returns the geometry associated with the given feature.
242
+
243
+ def get_feature_geometry(object_)
244
+ object_.geometry
245
+ end
246
+
247
+
248
+ # Returns the ID of the given feature, or nil for no ID.
249
+
250
+ def get_feature_id(object_)
251
+ object_.feature_id
252
+ end
253
+
254
+
255
+ # Returns the properties of the given feature as a hash. Editing
256
+ # this hash does not change the state of the feature.
257
+
258
+ def get_feature_properties(object_)
259
+ object_.properties
260
+ end
261
+
262
+
263
+ # Return the singleton instance of EntityFactory.
264
+
265
+ def self.instance
266
+ @instance ||= self.new
267
+ end
268
+
269
+
270
+ end
271
+
272
+
273
+ end
274
+
275
+ end