charta 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/charta.gemspec +21 -20
  3. data/lib/charta/bounding_box.rb +5 -1
  4. data/lib/charta/coordinates.rb +65 -0
  5. data/lib/charta/ewkt_serializer.rb +101 -0
  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 +12 -124
  20. data/lib/charta/geometry.rb +70 -16
  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 +6 -0
  27. data/lib/charta/polygon.rb +5 -0
  28. data/lib/charta/version.rb +1 -1
  29. data/lib/charta.rb +39 -87
  30. data/lib/rgeo/svg.rb +44 -44
  31. metadata +78 -44
  32. data/.gitignore +0 -11
  33. data/.gitlab-ci.yml +0 -14
  34. data/.travis.yml +0 -7
  35. data/CODE_OF_CONDUCT.md +0 -74
  36. data/Gemfile +0 -4
  37. data/LICENSE.txt +0 -21
  38. data/README.md +0 -44
  39. data/Rakefile +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5cef2f555beb27d13a0cbc3bbc24f64f9143d2cb
4
- data.tar.gz: 64f46d959388ca952edbb53e8deb4a887335bc9f
2
+ SHA256:
3
+ metadata.gz: 96bee13636a087302272ef4bc271ca0200e46aece932f932a2c9ff6b989254a8
4
+ data.tar.gz: 4d139f4c3d628b178862b6879edc2ba92e11f82e96f43d748def1ac063c89562
5
5
  SHA512:
6
- metadata.gz: 99614ef99edfdbd74e8d107279a1e8533b887cc3e4e503ba7355c60f964a4aeb95e5778debd35dc3fd4c385c61c3a199f412ac1252691accf0b079e60530b99d
7
- data.tar.gz: 4abce4b81dd5ccab25fcb6778b896615c627d389aec8cd465301595ff9e171f1af7c64bb1f1b7f3947d4d9b73ca5a16cc23c508303796e254f57a92ac0f7a56d
6
+ metadata.gz: 04b4a7e56bc7a412114f4f2713725d1304bc4c859911a2079a847b88511d80c5f20057df7a19f79c6b022b107ff9b1328b064084f0ebdf1e5335622afbff7f27
7
+ data.tar.gz: 3f8cec56a64a686abcfd14264df20b593c396fb0296b779b48c2697a630ad9bed6d16de61a86c12995fec6f74e5a1cf4ac1f65655e244a840393ea5448fd00ed
data/charta.gemspec CHANGED
@@ -1,30 +1,31 @@
1
- lib = File.expand_path('../lib', __FILE__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'charta/version'
1
+ require_relative 'lib/charta/version'
4
2
 
5
3
  Gem::Specification.new do |spec|
6
- spec.name = 'charta'
7
- spec.version = Charta::VERSION
8
- spec.authors = ['Brice TEXIER']
9
- spec.email = ['brice@ekylibre.com']
4
+ spec.name = 'charta'
5
+ spec.version = Charta::VERSION
6
+ spec.authors = ['Ekylibre developers']
7
+ spec.email = ['dev@ekylibre.com']
10
8
 
11
- spec.summary = 'Simple tool over geos and co'
12
- spec.homepage = 'https://github.com/ekylibre/charta'
13
- spec.license = 'MIT'
9
+ spec.summary = 'Simple tool over geos and co'
10
+ spec.required_ruby_version = '>= 2.6.0'
11
+ spec.homepage = 'https://gitlab.com/ekylibre'
12
+ spec.license = 'AGPL-3.0-only'
13
+
14
+ spec.files = Dir.glob(%w[lib/**/*.rb *.gemspec])
14
15
 
15
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
- f.match(%r{^(test|spec|features)/})
17
- end
18
16
  spec.require_paths = ['lib']
19
17
 
20
- spec.add_dependency 'nokogiri', '>= 1.7.0'
21
- spec.add_dependency 'rgeo', '~> 1.0'
22
- spec.add_dependency 'json', '>= 1.8.0'
23
- spec.add_dependency 'rgeo-geojson', '~> 1.0'
24
- spec.add_dependency 'rgeo-proj4', '~> 1.0'
25
18
  spec.add_dependency 'activesupport', '~> 5.0'
19
+ spec.add_dependency 'json', '>= 1.8.0'
20
+ spec.add_dependency 'nokogiri', '>= 1.7.0'
21
+ spec.add_dependency 'rgeo', '~> 2.0'
22
+ spec.add_dependency 'rgeo-geojson', '~> 2.0'
23
+ spec.add_dependency 'rgeo-proj4', '~> 2.0'
24
+ spec.add_dependency 'victor', '~> 0.3.3'
25
+ spec.add_dependency 'zeitwerk', '~> 2.4.0'
26
+
26
27
  spec.add_development_dependency 'bundler', '~> 2.0'
27
- spec.add_development_dependency 'rake', '~> 10.0'
28
28
  spec.add_development_dependency 'minitest', '~> 5.0'
29
- spec.add_development_dependency 'byebug'
29
+ spec.add_development_dependency 'rake', '~> 12.0'
30
+ spec.add_development_dependency 'rubocop', '1.3.1'
30
31
  end
@@ -18,11 +18,15 @@ module Charta
18
18
  end
19
19
 
20
20
  def svg_view_box
21
- [x_min, -y_max, width, height]
21
+ [x_min, y_min, width, height]
22
22
  end
23
23
 
24
24
  def to_a
25
25
  [[@y_min, @x_min], [@y_max, @x_max]]
26
26
  end
27
+
28
+ def to_bbox_string
29
+ "#{@x_min}, #{@y_min}, #{x_max}, #{y_max}"
30
+ end
27
31
  end
28
32
  end
@@ -0,0 +1,65 @@
1
+ module Charta
2
+ module Coordinates
3
+ class << self
4
+
5
+ # Force coordinates to 2D
6
+ def flatten(hash)
7
+ map_coordinates(hash) { |position| position[0..1] }
8
+ end
9
+
10
+ def map_coordinates(hash, &block)
11
+ case hash['type']
12
+ when 'FeatureCollection'
13
+ map_feature_collection_coordinates hash, &block
14
+ when 'Feature'
15
+ map_feature_coordinates hash, &block
16
+ else
17
+ map_geometry_coordinates hash, &block
18
+ end
19
+ end
20
+
21
+ def normalize_4326_geometry(json)
22
+ map_coordinates json do |(x, y)|
23
+ [((x + 180.to_d) % 360.to_d) - 180.to_d, ((y + 90.to_d) % 180.to_d) - 90.to_d]
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def map_feature_collection_coordinates(hash, &block)
30
+ hash.merge 'features' => hash['features'].map { |feature| map_feature_coordinates feature, &block }
31
+ end
32
+
33
+ def map_feature_coordinates(hash, &block)
34
+ hash.merge 'geometry' => map_geometry_coordinates(hash['geometry'], &block)
35
+ end
36
+
37
+ def map_geometry_coordinates(hash, &block)
38
+ if hash['type'] == 'GeometryCollection'
39
+ map_geometry_collection_coordinates hash, &block
40
+ else
41
+ coordinates = hash['coordinates']
42
+ mapped =
43
+ case hash['type']
44
+ when 'Point'
45
+ block.call coordinates
46
+ when 'MultiPoint', 'LineString'
47
+ coordinates.map(&block)
48
+ when 'MultiLineString', 'Polygon'
49
+ coordinates.map { |line| line.map(&block) }
50
+ when 'MultiPolygon'
51
+ coordinates.map { |poly| poly.map { |line| line.map(&block) } }
52
+ else
53
+ raise StandardError.new("Cannot handle: #{hash['type'].inspect}. In #{hash.inspect}")
54
+ end
55
+
56
+ hash.merge 'coordinates' => mapped
57
+ end
58
+ end
59
+
60
+ def map_geometry_collection_coordinates(hash, &block)
61
+ hash.merge 'geometries' => hash['geometries'].map { |geometry| map_geometry_coordinates(geometry, &block) }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,101 @@
1
+ module Charta
2
+ module EwktSerializer
3
+
4
+ class << self
5
+ def object_to_ewkt(hash)
6
+ type = hash[:type] || hash['type']
7
+ send("#{type.gsub(/(.)([A-Z])/, '\1_\2').downcase}_to_ewkt", hash)
8
+ end
9
+
10
+ private
11
+
12
+ def feature_collection_to_ewkt(hash)
13
+ return 'GEOMETRYCOLLECTION EMPTY' if hash['features'].nil?
14
+
15
+ 'GEOMETRYCOLLECTION(' + hash['features'].collect do |feature|
16
+ object_to_ewkt(feature)
17
+ end.join(', ') + ')'
18
+ end
19
+
20
+ def geometry_collection_to_ewkt(hash)
21
+ return 'GEOMETRYCOLLECTION EMPTY' if hash['geometries'].nil?
22
+
23
+ 'GEOMETRYCOLLECTION(' + hash['geometries'].collect do |feature|
24
+ object_to_ewkt(feature)
25
+ end.join(', ') + ')'
26
+ end
27
+
28
+ def feature_to_ewkt(hash)
29
+ object_to_ewkt(hash['geometry'])
30
+ end
31
+
32
+ def point_to_ewkt(hash)
33
+ return 'POINT EMPTY' if hash['coordinates'].nil?
34
+
35
+ 'POINT(' + hash['coordinates'].join(' ') + ')'
36
+ end
37
+
38
+ def line_string_to_ewkt(hash)
39
+ return 'LINESTRING EMPTY' if hash['coordinates'].nil?
40
+
41
+ 'LINESTRING(' + hash['coordinates'].collect do |point|
42
+ point.join(' ')
43
+ end.join(', ') + ')'
44
+ end
45
+
46
+ def polygon_to_ewkt(hash)
47
+ return 'POLYGON EMPTY' if hash['coordinates'].nil?
48
+
49
+ 'POLYGON(' + hash['coordinates'].collect do |hole|
50
+ '(' + hole.collect do |point|
51
+ point.join(' ')
52
+ end.join(', ') + ')'
53
+ end.join(', ') + ')'
54
+ end
55
+
56
+ def multi_point_to_ewkt(hash)
57
+ return 'MULTIPOINT EMPTY' if hash['coordinates'].nil?
58
+
59
+ 'MULTIPOINT(' + hash['coordinates'].collect do |point|
60
+ '(' + point.join(' ') + ')'
61
+ end.join(', ') + ')'
62
+ end
63
+
64
+ def multi_line_string_to_ewkt(hash)
65
+ return 'MULTILINESTRING EMPTY' if hash['coordinates'].nil?
66
+
67
+ 'MULTILINESTRING(' + hash['coordinates'].collect do |line|
68
+ '(' + line.collect do |point|
69
+ point.join(' ')
70
+ end.join(', ') + ')'
71
+ end.join(', ') + ')'
72
+ end
73
+
74
+ def multipolygon_to_ewkt(hash)
75
+ return 'MULTIPOLYGON EMPTY' if hash['coordinates'].nil?
76
+
77
+ 'MULTIPOLYGON(' + hash['coordinates'].collect do |polygon|
78
+ '(' + polygon.collect do |hole|
79
+ '(' + hole.collect do |point|
80
+ point.join(' ')
81
+ end.join(', ') + ')'
82
+ end.join(', ') + ')'
83
+ end.join(', ') + ')'
84
+ end
85
+
86
+ # for PostGIS ST_ASGeoJSON compatibility
87
+ def multi_polygon_to_ewkt(hash)
88
+ return 'MULTIPOLYGON EMPTY' if hash['coordinates'].nil?
89
+
90
+ 'MULTIPOLYGON(' + hash['coordinates'].collect do |polygon|
91
+ '(' + polygon.collect do |hole|
92
+ '(' + hole.collect do |point|
93
+ point.join(' ')
94
+ end.join(', ') + ')'
95
+ end.join(', ') + ')'
96
+ end.join(', ') + ')'
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ class EwktFeatureBuilder
6
+ # @param [String] ewkt EWKT representation of a feature
7
+ # @return [RGeo::Feature::Instance]
8
+ def from_ewkt(ewkt)
9
+ if ewkt.to_s =~ /\A[[:space:]]*\z/
10
+ raise ArgumentError.new("Invalid data: #{ewkt.inspect}")
11
+ end
12
+
13
+ Geometry.feature(ewkt)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ class FeatureFactoryBase
6
+ def new_feature(coordinates, srs: nil, format: nil)
7
+ raise StandardError.new('Not implemented')
8
+ end
9
+
10
+ def empty_feature(srs = :WGS84)
11
+ new_feature('GEOMETRYCOLLECTION EMPTY', srs: srs)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ class SimpleFeatureFactory < FeatureFactoryBase
6
+ class << self
7
+ def build
8
+ new(
9
+ ewkt_builder: EwktFeatureBuilder.new,
10
+ srid_provider: SridProvider.build,
11
+ transformer: Transformers::EwktTransformerChain.build
12
+ )
13
+ end
14
+ end
15
+
16
+ # @return [EwktFeatureBuilder]
17
+ attr_reader :ewkt_builder
18
+ # @return [SridProvider]
19
+ attr_reader :srid_provider
20
+ # @return [Transformers::EwktTransformer]
21
+ attr_reader :transformer
22
+
23
+ def initialize(ewkt_builder:, srid_provider:, transformer:)
24
+ @ewkt_builder = ewkt_builder
25
+ @srid_provider = srid_provider
26
+ @transformer = transformer
27
+ end
28
+
29
+ def new_feature(coordinates, srs: nil, format: nil)
30
+ if coordinates.is_a?(Charta::Geometry)
31
+ coordinates
32
+ elsif coordinates.is_a?(RGeo::Feature::Instance)
33
+ Geometry.feature(coordinates)
34
+ elsif coordinates.to_s =~ /\A[[:space:]]*\z/
35
+ empty_feature(srs)
36
+ else
37
+ convert_feature(coordinates, srs: srs, format: format)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def convert_feature(coordinates, srs: nil, format: nil)
44
+ srid = srs.nil? ? nil : srid_provider.find(srs)
45
+
46
+ ewkt_builder.from_ewkt(transformer.transform(coordinates, srid: srid, format: format))
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Charta
4
+ module Factory
5
+ class SimpleGeometryFactory
6
+ # @return [SimpleFeatureFactory]
7
+ attr_reader :feature_factory
8
+
9
+ # @param [SimpleFeatureFactory] feature_factory
10
+ def initialize(feature_factory:)
11
+ @feature_factory = feature_factory
12
+ end
13
+
14
+ def new_geometry(coordinates, srs: nil, format: nil)
15
+ if coordinates.is_a?(::Charta::Geometry)
16
+ coordinates
17
+ else
18
+ wrap(feature_factory.new_feature(coordinates, srs: srs, format: format))
19
+ end
20
+ end
21
+
22
+ def empty_geometry(srs)
23
+ wrap(feature_factory.empty_feature(srs))
24
+ end
25
+
26
+ protected
27
+
28
+ def wrap(feature)
29
+ case feature.geometry_type
30
+ when RGeo::Feature::Point
31
+ Point.new(feature)
32
+ when RGeo::Feature::LineString
33
+ LineString.new(feature)
34
+ when RGeo::Feature::Polygon
35
+ Polygon.new(feature)
36
+ when RGeo::Feature::MultiPolygon
37
+ MultiPolygon.new(feature)
38
+ when RGeo::Feature::GeometryCollection
39
+ GeometryCollection.new(feature)
40
+ else
41
+ Geometry.new(feature)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -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