charta 0.1.18 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/charta.gemspec +21 -19
  3. data/lib/charta.rb +38 -89
  4. data/lib/charta/coordinates.rb +17 -17
  5. data/lib/charta/ewkt_serializer.rb +10 -1
  6. data/lib/charta/factory/ewkt_feature_builder.rb +17 -0
  7. data/lib/charta/factory/feature_factory_base.rb +15 -0
  8. data/lib/charta/factory/simple_feature_factory.rb +50 -0
  9. data/lib/charta/factory/simple_geometry_factory.rb +46 -0
  10. data/lib/charta/factory/srid_provider.rb +36 -0
  11. data/lib/charta/factory/transformers/ewkt_passthrough.rb +20 -0
  12. data/lib/charta/factory/transformers/ewkt_transformer.rb +20 -0
  13. data/lib/charta/factory/transformers/ewkt_transformer_chain.rb +42 -0
  14. data/lib/charta/factory/transformers/from_geo_json_transformer.rb +20 -0
  15. data/lib/charta/factory/transformers/from_gml_transformer.rb +20 -0
  16. data/lib/charta/factory/transformers/from_kml_transformer.rb +20 -0
  17. data/lib/charta/factory/transformers/from_wkb_transformer.rb +24 -0
  18. data/lib/charta/factory/transformers/transformation_error.rb +10 -0
  19. data/lib/charta/geo_json.rb +4 -1
  20. data/lib/charta/geometry.rb +45 -25
  21. data/lib/charta/geometry_collection.rb +6 -4
  22. data/lib/charta/gml.rb +18 -2
  23. data/lib/charta/gml_import.rb +24 -24
  24. data/lib/charta/kml.rb +21 -3
  25. data/lib/charta/multi_polygon.rb +1 -1
  26. data/lib/charta/point.rb +2 -1
  27. data/lib/charta/version.rb +1 -1
  28. data/lib/rgeo/svg.rb +44 -44
  29. metadata +84 -37
  30. data/.gitignore +0 -12
  31. data/.gitlab-ci.yml +0 -13
  32. data/.travis.yml +0 -7
  33. data/CODE_OF_CONDUCT.md +0 -74
  34. data/Gemfile +0 -4
  35. data/LICENSE.txt +0 -21
  36. data/README.md +0 -44
  37. data/Rakefile +0 -10
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ class SridProvider
6
+ SRS = {
7
+ WGS84: 4326,
8
+ CRS84: 4326,
9
+ RGF93: 2143
10
+ }.freeze
11
+
12
+ class << self
13
+ def build
14
+ new(SRS)
15
+ end
16
+ end
17
+
18
+ def initialize(srs)
19
+ @srs = srs
20
+ end
21
+
22
+ def find(srname_or_srid)
23
+ if srname_or_srid.to_s =~ /\Aurn:ogc:def:crs:.*\z/
24
+ x = srname_or_srid.split(':').last.upcase.to_sym
25
+ @srs[x] || x
26
+ elsif srname_or_srid.to_s =~ /\AEPSG::?(\d{4,5})\z/
27
+ srname_or_srid.split(':').last
28
+ elsif srname_or_srid.to_s =~ /\A\d+\z/
29
+ srname_or_srid.to_i
30
+ else
31
+ @srs[srname_or_srid] || srname_or_srid
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ module Transformers
6
+ class EwktPassthrough < EwktTransformer
7
+ # @return [Boolean]
8
+ def handles?(value, format:)
9
+ value.is_a?(String) && format.nil?
10
+ end
11
+
12
+ # @param [String, Hash] value
13
+ # @return [String] ewkt representation of value
14
+ def transform(value, srid: nil, format: nil)
15
+ value
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ module Transformers
6
+ class EwktTransformer
7
+ # @return [Boolean]
8
+ def handles?(value, format:)
9
+ false
10
+ end
11
+
12
+ # @param [String, Hash] value
13
+ # @return [String] ewkt representation of value
14
+ def transform(value, srid: nil, format: nil)
15
+ raise StandardError.new('Not implemented')
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ module Transformers
6
+ class EwktTransformerChain < EwktTransformer
7
+ class << self
8
+ def build
9
+ new(
10
+ Transformers::FromGeoJsonTransformer.new,
11
+ Transformers::FromWkbTransformer.new,
12
+ Transformers::FromGmlTransformer.new,
13
+ Transformers::FromKmlTransformer.new,
14
+ Transformers::EwktPassthrough.new
15
+ )
16
+ end
17
+ end
18
+
19
+ # @return [Array<EwktTransformer>]
20
+ attr_reader :transformers
21
+
22
+ def initialize(*transformers)
23
+ @transformers = transformers
24
+ end
25
+
26
+ # @return [Boolean]
27
+ def handles?(value, format:)
28
+ transformers.any? { |t| t.handles?(value, format: format) }
29
+ end
30
+
31
+ # @param [String, Hash] value
32
+ # @return [String] ewkt representation of value
33
+ def transform(value, srid: nil, format: nil)
34
+ transformer = transformers.detect { |t| t.handles?(value, format: format) }
35
+ raise TransformationError.new('Not handled') if transformer.nil?
36
+
37
+ transformer.transform(value, srid: srid, format: format)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ module Transformers
6
+ class FromGeoJsonTransformer < EwktTransformer
7
+ # @return [Boolean]
8
+ def handles?(value, format:)
9
+ value.is_a?(Hash) || (value.is_a?(String) && Charta::GeoJSON.valid?(value)) # GeoJSON
10
+ end
11
+
12
+ # @param [String, Hash] value
13
+ # @return [String] ewkt representation of value
14
+ def transform(value, srid: nil, format: nil)
15
+ Charta::GeoJSON.new(value, srid).to_ewkt
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ module Transformers
6
+ class FromGmlTransformer < EwktTransformer
7
+ # @return [Boolean]
8
+ def handles?(value, format:)
9
+ value.is_a?(String) && format == 'gml' && Charta::GML.valid?(value)
10
+ end
11
+
12
+ # @param [String, Hash] value
13
+ # @return [String] ewkt representation of value
14
+ def transform(value, srid: nil, format: nil)
15
+ Charta::GML.new(value, srid).to_ewkt
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ module Transformers
6
+ class FromKmlTransformer < EwktTransformer
7
+ # @return [Boolean]
8
+ def handles?(value, format:)
9
+ value.is_a?(String) && format == 'kml' && Charta::KML.valid?(value)
10
+ end
11
+
12
+ # @param [String, Hash] value
13
+ # @return [String] ewkt representation of value
14
+ def transform(value, srid: nil, format: nil)
15
+ Charta::KML.new(value, srid).to_ewkt
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ module Transformers
6
+ class FromWkbTransformer < EwktTransformer
7
+ # @return [Boolean]
8
+ def handles?(value, format:)
9
+ value.is_a?(String) && !!(value =~ /\A[A-F0-9]+\z/)
10
+ end
11
+
12
+ # @param [String, Hash] value
13
+ # @return [String] ewkt representation of value
14
+ def transform(value, srid: nil, format: nil)
15
+ if srid.nil?
16
+ Geometry.factory.parse_wkb(value)
17
+ else
18
+ RGeo::Geos.factory(srid: srid).parse_wkb(value)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ module Transformers
6
+ class TransformationError < StandardError
7
+ end
8
+ end
9
+ end
10
+ end
@@ -48,7 +48,10 @@ module Charta
48
48
  Coordinates.flatten hash
49
49
  end
50
50
 
51
- %i[object_to_ewkt feature_collection_to_ewkt geometry_collection_to_ewkt feature_to_ewkt point_to_ewkt line_string_to_ewkt polygon_to_ewkt multi_point_to_ewkt multi_line_string_to_ewkt multipolygon_to_ewkt multi_polygon_to_ewkt].each do |m|
51
+ %i[
52
+ object_to_ewkt feature_collection_to_ewkt geometry_collection_to_ewkt feature_to_ewkt point_to_ewkt line_string_to_ewkt
53
+ polygon_to_ewkt multi_point_to_ewkt multi_line_string_to_ewkt multipolygon_to_ewkt multi_polygon_to_ewkt
54
+ ].each do |m|
52
55
  define_method m do |*args|
53
56
  EwktSerializer.send m, *args
54
57
  end
@@ -1,6 +1,7 @@
1
1
  require 'json'
2
2
  require 'rgeo/geo_json'
3
3
  require 'rgeo/svg' # integrated lib for now
4
+ require 'active_support/core_ext/module/delegation'
4
5
 
5
6
  module Charta
6
7
  # Represents a Geometry with SRID
@@ -33,11 +34,6 @@ module Charta
33
34
  feature.srid.to_i
34
35
  end
35
36
 
36
- # Returns the underlaying object managed by Charta: the RGeo feature
37
- def to_rgeo
38
- feature
39
- end
40
-
41
37
  # Returns the Well-Known Text (WKT) representation of the geometry/geography
42
38
  # without SRID metadata
43
39
  def to_text
@@ -101,6 +97,7 @@ module Charta
101
97
  other_geometry = Charta.new_geometry(other).transform(srid)
102
98
  return true if empty? && other_geometry.empty?
103
99
  return inspect == other_geometry.inspect if collection? && other_geometry.collection?
100
+
104
101
  feature.equals?(other_geometry.feature)
105
102
  end
106
103
 
@@ -109,6 +106,7 @@ module Charta
109
106
  other_geometry = Charta.new_geometry(other).transform(srid)
110
107
  return true if empty? && other_geometry.empty?
111
108
  return inspect == other_geometry.inspect if collection? && other_geometry.collection?
109
+
112
110
  !feature.equals?(other_geometry.feature)
113
111
  end
114
112
 
@@ -146,6 +144,7 @@ module Charta
146
144
  # of mass of the geometry as a POINT.
147
145
  def centroid
148
146
  return nil unless surface? && !feature.is_empty?
147
+
149
148
  point = feature.centroid
150
149
  [point.y, point.x]
151
150
  end
@@ -153,22 +152,23 @@ module Charta
153
152
  # Returns a POINT guaranteed to lie on the surface.
154
153
  def point_on_surface
155
154
  return nil unless surface?
155
+
156
156
  point = feature.point_on_surface
157
157
  [point.y, point.x]
158
158
  end
159
159
 
160
160
  def convert_to(new_type)
161
161
  case new_type
162
- when type then
163
- self
164
- when :multi_point then
165
- flatten_multi(:point)
166
- when :multi_line_string then
167
- flatten_multi(:line_string)
168
- when :multi_polygon then
169
- flatten_multi(:polygon)
170
- else
171
- self
162
+ when type
163
+ self
164
+ when :multi_point
165
+ flatten_multi(:point)
166
+ when :multi_line_string
167
+ flatten_multi(:line_string)
168
+ when :multi_polygon
169
+ flatten_multi(:polygon)
170
+ else
171
+ self
172
172
  end
173
173
  end
174
174
 
@@ -196,10 +196,12 @@ module Charta
196
196
  def transform(new_srid)
197
197
  return self if new_srid == srid
198
198
  raise 'Proj is not supported. Cannot tranform' unless RGeo::CoordSys::Proj4.supported?
199
+
199
200
  new_srid = Charta::SRS[new_srid] || new_srid
200
201
  database = self.class.srs_database
201
202
  new_proj_entry = database.get(new_srid)
202
203
  raise "Cannot find proj for SRID: #{new_srid}" if new_proj_entry.nil?
204
+
203
205
  new_feature = RGeo::CoordSys::Proj4.transform(
204
206
  database.get(srid).proj4,
205
207
  feature,
@@ -224,8 +226,7 @@ module Charta
224
226
 
225
227
  def intersection(other)
226
228
  other_geometry = Charta.new_geometry(other).transform(srid)
227
-
228
- Charta.new_geometry(feature.intersection(other_geometry.feature))
229
+ feature.intersection(other_geometry.feature)
229
230
  end
230
231
 
231
232
  def intersects?(other)
@@ -262,22 +263,24 @@ module Charta
262
263
  Charta.find_srid(name_or_srid)
263
264
  end
264
265
 
265
- # TODO: Manage YAML domain type to ensure maintainability of YAML
266
- # serialization in time.
266
+ # Returns the underlaying object managed by Charta: the RGeo feature
267
267
  def feature
268
- if @feature.nil?
269
- if @ewkt.nil?
270
- raise StandardError, 'Invalid geometry (no feature, no EWKT)'
271
- else
268
+ unless defined? @feature
269
+ if defined? @ewkt
272
270
  @feature = ::Charta::Geometry.from_ewkt(@ewkt)
273
271
  @properties = @options.dup if @options
272
+ else
273
+ raise StandardError.new('Invalid geometry (no feature, no EWKT)')
274
274
  end
275
275
  end
276
276
  @feature.dup
277
277
  end
278
278
 
279
+ alias to_rgeo feature
280
+
279
281
  def feature=(new_feature)
280
- raise ArgumentError, "Feature can't be nil" if new_feature.nil?
282
+ raise ArgumentError.new("Feature can't be nil") if new_feature.nil?
283
+
281
284
  @feature = new_feature
282
285
  end
283
286
 
@@ -285,6 +288,21 @@ module Charta
285
288
  { type: 'Feature', properties: properties, geometry: to_json_object }
286
289
  end
287
290
 
291
+ def method_missing(name, *args, &block)
292
+ target = to_rgeo
293
+ if target.respond_to? name
294
+ target.send name, *args
295
+ else
296
+ raise StandardError.new("Method #{name} does not exist for #{self.class.name}")
297
+ end
298
+ end
299
+
300
+ def respond_to_missing?(name, include_private = false)
301
+ return false if name == :init_with
302
+
303
+ super
304
+ end
305
+
288
306
  class << self
289
307
  def srs_database
290
308
  @srs_database ||= RGeo::CoordSys::SRSDatabase::Proj4Data.new('epsg', authority: 'EPSG', cache: true)
@@ -292,11 +310,13 @@ module Charta
292
310
 
293
311
  def factory(srid = 4326)
294
312
  return projected_factory(srid) if srid.to_i == 4326
313
+
295
314
  geos_factory(srid)
296
315
  end
297
316
 
298
317
  def feature(ewkt_or_rgeo)
299
318
  return from_rgeo(ewkt_or_rgeo) if ewkt_or_rgeo.is_a? RGeo::Feature::Instance
319
+
300
320
  from_ewkt(ewkt_or_rgeo)
301
321
  end
302
322
 
@@ -307,7 +327,7 @@ module Charta
307
327
 
308
328
  def from_ewkt(ewkt)
309
329
  # Cleans empty geometries
310
- ewkt.gsub!(/(GEOMETRYCOLLECTION|GEOMETRY|((MULTI)?(POINT|LINESTRING|POLYGON)))\(\)/, '\1 EMPTY')
330
+ ewkt = ewkt.gsub(/(GEOMETRYCOLLECTION|GEOMETRY|((MULTI)?(POINT|LINESTRING|POLYGON)))\(\)/, '\1 EMPTY')
311
331
  srs = ewkt.split(/[\=\;]+/)[0..1]
312
332
  srid = nil
313
333
  srid = srs[1] if srs[0] =~ /srid/i
@@ -1,10 +1,12 @@
1
1
  module Charta
2
2
  # Represent a Geometry with contains other geometries
3
3
  class GeometryCollection < Geometry
4
- def self.empty(srid = nil)
5
- srid = Charta.find_srid(srid.nil? ? :WGS84 : srid)
6
- feature = Charta.new_feature('GEOMETRYCOLLECTION EMPTY', srid)
7
- new(feature)
4
+ class << self
5
+ def empty(srid = nil)
6
+ srid = Charta.find_srid(srid.nil? ? :WGS84 : srid)
7
+ feature = Charta.new_feature('GEOMETRYCOLLECTION EMPTY', srid)
8
+ new(feature)
9
+ end
8
10
  end
9
11
 
10
12
  def to_json_feature_collection(collection_properties = [])
data/lib/charta/gml.rb CHANGED
@@ -83,6 +83,7 @@ module Charta
83
83
  'GEOMETRYCOLLECTION(' + gml.css("#{GML_PREFIX}|featureMember").collect do |feature|
84
84
  TAGS.collect do |tag|
85
85
  next if feature.css("#{GML_PREFIX}|#{tag}").empty?
86
+
86
87
  feature.css("#{GML_PREFIX}|#{tag}").collect do |fragment|
87
88
  object_to_ewkt(fragment, srid)
88
89
  end.compact.join(', ')
@@ -90,6 +91,7 @@ module Charta
90
91
  end.compact.join(', ') + ')'
91
92
  end
92
93
  end
94
+
93
95
  alias geometry_collection_to_ewkt document_to_ewkt
94
96
 
95
97
  def transform(data, from_srid, to_srid)
@@ -101,8 +103,9 @@ module Charta
101
103
 
102
104
  wkt = 'POLYGON(' + %w[outerBoundaryIs innerBoundaryIs].collect do |boundary|
103
105
  next if gml.css("#{GML_PREFIX}|#{boundary}").empty?
106
+
104
107
  gml.css("#{GML_PREFIX}|#{boundary}").collect do |hole|
105
- '(' + hole.css("#{GML_PREFIX}|coordinates").collect { |coords| coords.content.split(/\r\n|\n| /) }.flatten.reject(&:empty?).collect { |c| c.split ',' }.collect { |dimension| %(#{dimension.first} #{dimension[1]}) }.join(', ') + ')'
108
+ "(#{transform_coordinates(hole)})"
106
109
  end.join(', ')
107
110
  end.compact.join(', ') + ')'
108
111
 
@@ -115,6 +118,7 @@ module Charta
115
118
 
116
119
  def point_to_ewkt(gml, srid)
117
120
  return 'POINT EMPTY' if gml.css("#{GML_PREFIX}|coordinates").nil?
121
+
118
122
  wkt = 'POINT(' + gml.css("#{GML_PREFIX}|coordinates").collect { |coords| coords.content.split ',' }.flatten.join(' ') + ')'
119
123
 
120
124
  unless gml['srsName'].nil? || Charta.find_srid(gml['srsName']).to_s == srid.to_s
@@ -127,7 +131,7 @@ module Charta
127
131
  def line_string_to_ewkt(gml, srid)
128
132
  return 'LINESTRING EMPTY' if gml.css("#{GML_PREFIX}|coordinates").nil?
129
133
 
130
- wkt = 'LINESTRING(' + gml.css("#{GML_PREFIX}|coordinates").collect { |coords| coords.content.split(/\r\n|\n| /) }.flatten.reject(&:empty?).collect { |c| c.split ',' }.collect { |dimension| %(#{dimension.first} #{dimension[1]}) }.join(', ') + ')'
134
+ wkt = "LINESTRING(#{transform_coordinates(gml)})"
131
135
 
132
136
  unless gml['srsName'].nil? || Charta.find_srid(gml['srsName']).to_s == srid.to_s
133
137
  wkt = transform(wkt, Charta.find_srid(gml['srsName']), srid)
@@ -135,6 +139,18 @@ module Charta
135
139
 
136
140
  wkt
137
141
  end
142
+
143
+ private
144
+
145
+ def transform_coordinates(coordinates)
146
+ coordinates.css("#{GML_PREFIX}|coordinates")
147
+ .collect { |coords| coords.content.split(/\r\n|\n| /) }
148
+ .flatten
149
+ .reject(&:empty?)
150
+ .collect { |c| c.split ',' }
151
+ .collect { |dimension| %(#{dimension.first} #{dimension[1]}) }
152
+ .join(', ')
153
+ end
138
154
  end
139
155
  end
140
156
  end