charta 0.2.0 → 0.3.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.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/charta.gemspec +21 -21
  3. data/lib/charta.rb +39 -87
  4. data/lib/charta/bounding_box.rb +4 -0
  5. data/lib/charta/coordinates.rb +65 -0
  6. data/lib/charta/ewkt_serializer.rb +101 -0
  7. data/lib/charta/factory/ewkt_feature_builder.rb +17 -0
  8. data/lib/charta/factory/feature_factory_base.rb +15 -0
  9. data/lib/charta/factory/simple_feature_factory.rb +50 -0
  10. data/lib/charta/factory/simple_geometry_factory.rb +46 -0
  11. data/lib/charta/factory/srid_provider.rb +36 -0
  12. data/lib/charta/factory/transformers/ewkt_passthrough.rb +20 -0
  13. data/lib/charta/factory/transformers/ewkt_transformer.rb +20 -0
  14. data/lib/charta/factory/transformers/ewkt_transformer_chain.rb +42 -0
  15. data/lib/charta/factory/transformers/from_geo_json_transformer.rb +20 -0
  16. data/lib/charta/factory/transformers/from_gml_transformer.rb +20 -0
  17. data/lib/charta/factory/transformers/from_kml_transformer.rb +20 -0
  18. data/lib/charta/factory/transformers/from_wkb_transformer.rb +24 -0
  19. data/lib/charta/factory/transformers/transformation_error.rb +10 -0
  20. data/lib/charta/geo_json.rb +12 -124
  21. data/lib/charta/geometry.rb +111 -68
  22. data/lib/charta/geometry_collection.rb +6 -4
  23. data/lib/charta/gml.rb +18 -2
  24. data/lib/charta/gml_import.rb +24 -24
  25. data/lib/charta/kml.rb +21 -3
  26. data/lib/charta/multi_polygon.rb +1 -1
  27. data/lib/charta/point.rb +6 -0
  28. data/lib/charta/polygon.rb +5 -0
  29. data/lib/charta/version.rb +1 -1
  30. data/lib/rgeo/svg.rb +44 -44
  31. metadata +76 -55
  32. data/.gitignore +0 -11
  33. data/.travis.yml +0 -7
  34. data/CODE_OF_CONDUCT.md +0 -74
  35. data/Gemfile +0 -4
  36. data/LICENSE.txt +0 -21
  37. data/README.md +0 -44
  38. data/Rakefile +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 94a236f9258dcbbbaa9a215a5af25e1ddeb904bb
4
- data.tar.gz: 8251595d5fc8c7a70881108b37b43507b694f034
2
+ SHA256:
3
+ metadata.gz: a9634870840f818207264c46799dc53cf3e37bf3f3d119e85dc470405ebcb8d1
4
+ data.tar.gz: 6453c04a73201f197c869b37d18a6ff0c262c8545ced384a13ecee48568279e1
5
5
  SHA512:
6
- metadata.gz: 2440f9a26b7cd4d3d14c5f8b3840f3be1ba51ac7e11269c4ef2d5e615956d87f652b210b70861d803f11a1748533e67166c33732c946eac46f31e57470bf1496
7
- data.tar.gz: a426cc2d10067261ad61666d63373cb7e28be1ac6c5b4887926b44d1ae7efe8e3d089e4092d19e791d6b5dc9df9ee6bbd96faa687568ae8253c3e367b7bfc6a2
6
+ metadata.gz: 658472714de5dbe3a618839ddf36858884b6d28ab520bab2c5140514d0a287931b52aa1278110dc3979a3ed40b2bc0c7cb7e11a9450bff9767ae32180ebe24f8
7
+ data.tar.gz: 675ea3982c1d1bb3465f68e272fb995dde17cc61f5af81837346a7431fc79d1fc82dc0661eb2528aab2903808d6e0941d20923fa62142db25eafa95eb32a00f6
data/charta.gemspec CHANGED
@@ -1,30 +1,30 @@
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.0'
18
+ spec.add_dependency 'activesupport', '~> 5.0'
22
19
  spec.add_dependency 'json', '>= 1.8.0'
23
- spec.add_dependency 'rgeo-geojson', '~> 1.0.0'
24
- spec.add_dependency 'rgeo-proj4', '~> 1.0.0'
25
- spec.add_dependency 'activesupport', '>= 5.1'
26
- spec.add_development_dependency 'bundler', '~> 1.14'
27
- spec.add_development_dependency 'rake', '~> 10.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 'zeitwerk', '~> 2.4.0'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 2.0'
28
27
  spec.add_development_dependency 'minitest', '~> 5.0'
29
- spec.add_development_dependency 'byebug'
28
+ spec.add_development_dependency 'rake', '~> 12.0'
29
+ spec.add_development_dependency 'rubocop', '1.3.1'
30
30
  end
data/lib/charta.rb CHANGED
@@ -1,17 +1,18 @@
1
1
  # Gathers geomatic calculations
2
2
  # Completes RGeo
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
3
5
  require 'rgeo'
4
6
  require 'rgeo/proj4'
5
- require 'charta/geometry'
6
- require 'charta/geometry_collection'
7
- require 'charta/point'
8
- require 'charta/line_string'
9
- require 'charta/polygon'
10
- require 'charta/multi_polygon'
11
- require 'charta/bounding_box'
12
- require 'charta/geo_json'
13
- require 'charta/gml'
14
- require 'charta/kml'
7
+ require 'zeitwerk'
8
+
9
+ loader = Zeitwerk::Loader.for_gem
10
+ loader.inflector.inflect(
11
+ 'geo_json' => 'GeoJSON',
12
+ 'gml' => 'GML',
13
+ 'kml' => 'KML'
14
+ )
15
+ loader.setup
15
16
 
16
17
  unless RGeo::CoordSys::Proj4.supported?
17
18
  puts "Proj4 is not supported. Some actions won't work"
@@ -26,75 +27,27 @@ module Charta
26
27
  }.freeze
27
28
 
28
29
  class << self
30
+ def default_feature_factory=(factory)
31
+ @default_feature_factory = factory
32
+ @geometry_factory = nil
33
+ end
34
+
35
+ # @deprecated This is deprecated and will be removed in 0.4
36
+ def default_feature_factory
37
+ @default_feature_factory || (self.default_feature_factory = Factory::SimpleFeatureFactory.build)
38
+ end
39
+
40
+ def geometry_factory
41
+ @geometry_factory ||= Factory::SimpleGeometryFactory.new(feature_factory: default_feature_factory)
42
+ end
43
+
44
+ # @deprecated This is deprecated and will be removed in 0.4
29
45
  def new_feature(coordinates, srs = nil, format = nil, _flatten_collection = true, _options = {})
30
- geom_ewkt = nil
31
- if coordinates.is_a?(RGeo::Feature::Instance)
32
- return Geometry.feature(coordinates)
33
- elsif coordinates.is_a?(::Charta::Geometry)
34
- return coordinates
35
- elsif coordinates.to_s =~ /\A[[:space:]]*\z/
36
- geom_ewkt = empty_geometry(srs).to_ewkt
37
- elsif coordinates.is_a?(Hash) || (coordinates.is_a?(String) && ::Charta::GeoJSON.valid?(coordinates)) # GeoJSON
38
- srid = srs ? find_srid(srs) : :WGS84
39
- geom_ewkt = ::Charta::GeoJSON.new(coordinates, srid).to_ewkt
40
- elsif coordinates.is_a?(String)
41
- geom_ewkt = if coordinates =~ /\A[A-F0-9]+\z/ # WKB
42
- if srs && srid = find_srid(srs)
43
- generate_ewkt RGeo::Geos.factory(srid: srid).parse_wkb(coordinates)
44
- else
45
- generate_ewkt Geometry.factory.parse_wkb(coordinates)
46
- end
47
- elsif format == 'gml' && ::Charta::GML.valid?(coordinates)
48
- # required format 'cause kml geometries return empty instead of failing
49
- ::Charta::GML.new(coordinates, srid).to_ewkt
50
- elsif format == 'kml' && ::Charta::KML.valid?(coordinates)
51
- ::Charta::KML.new(coordinates).to_ewkt
52
- elsif coordinates =~ /^SRID\=\d+\;/i
53
- if feature = Geometry.feature(coordinates)
54
- generate_ewkt feature
55
- else
56
- Charta::GeometryCollection.empty.feature
57
- end
58
- else # WKT expected
59
- if srs && srid = find_srid(srs)
60
- begin
61
- f = RGeo::Geos.factory(srid: srid).parse_wkt(coordinates)
62
- rescue RGeo::Error::ParseError => e
63
- raise "Invalid EWKT (#{e.message}): #{coordinates}"
64
- end
65
- generate_ewkt f
66
- else
67
- generate_ewkt Geometry.feature(coordinates)
68
- end
69
- end
70
- else # Default for RGeo
71
- geom_ewkt = generate_ewkt coordinates
72
- end
73
- if geom_ewkt.to_s =~ /\A[[:space:]]*\z/
74
- raise ArgumentError, "Invalid data: coordinates=#{coordinates.inspect}, srid=#{srid.inspect}"
75
- end
76
- Geometry.feature(geom_ewkt)
46
+ default_feature_factory.new_feature(coordinates, srs: srs, format: format)
77
47
  end
78
48
 
79
49
  def new_geometry(coordinates, srs = nil, format = nil, _flatten_collection = true, _options = {})
80
- return coordinates if coordinates.is_a?(::Charta::Geometry)
81
- feature = Charta.new_feature(coordinates, srs, format, _flatten_collection, _options)
82
- type = feature.geometry_type
83
- geom = case type
84
- when RGeo::Feature::Point then
85
- Point.new(feature)
86
- when RGeo::Feature::LineString then
87
- LineString.new(feature)
88
- when RGeo::Feature::Polygon then
89
- Polygon.new(feature)
90
- when RGeo::Feature::MultiPolygon then
91
- MultiPolygon.new(feature)
92
- when RGeo::Feature::GeometryCollection then
93
- GeometryCollection.new(feature)
94
- else
95
- Geometry.new(feature)
96
- end
97
- geom
50
+ geometry_factory.new_geometry(coordinates, srs: srs, format: format)
98
51
  end
99
52
 
100
53
  def new_point(lat, lon, srid = 4326)
@@ -106,12 +59,18 @@ module Charta
106
59
  options[:srid] ||= new_geometry(points.first).srid if points.any?
107
60
  options[:srid] ||= 4326
108
61
 
109
- ewkt = "SRID=#{options[:srid]};LINESTRING(" + points.map { |wkt| p = new_geometry(wkt); "#{p.x} #{p.y}" }.join(', ') + ')'
62
+ points_coordinates = points.map do |wkt|
63
+ p = new_geometry(wkt)
64
+
65
+ "#{p.x} #{p.y}"
66
+ end
67
+
68
+ ewkt = "SRID=#{options[:srid]};LINESTRING(#{points_coordinates.join(', ')})"
110
69
  new_geometry(ewkt)
111
70
  end
112
71
 
113
72
  def empty_geometry(srid = :WGS84)
114
- GeometryCollection.empty(srid)
73
+ geometry_factory.empty_geometry(srid)
115
74
  end
116
75
 
117
76
  def generate_ewkt(feature)
@@ -138,23 +97,16 @@ module Charta
138
97
  end
139
98
 
140
99
  # Check and returns the SRID matching with srname or SRID.
100
+ # @deprecated
141
101
  def find_srid(srname_or_srid)
142
- if srname_or_srid.to_s =~ /\Aurn:ogc:def:crs:.*\z/
143
- x = srname_or_srid.split(':').last.upcase.to_sym
144
- SRS[x] || x
145
- elsif srname_or_srid.to_s =~ /\AEPSG::?(\d{4,5})\z/
146
- srname_or_srid.split(':').last
147
- elsif srname_or_srid.to_s =~ /\A\d+\z/
148
- srname_or_srid.to_i
149
- else
150
- SRS[srname_or_srid] || srname_or_srid
151
- end
102
+ Factory::SridProvider.build.find(srname_or_srid)
152
103
  end
153
104
 
154
105
  def from(format, data)
155
106
  unless respond_to?("from_#{format}")
156
107
  raise "Unknown format: #{format.inspect}"
157
108
  end
109
+
158
110
  send("from_#{format}", data)
159
111
  end
160
112
 
@@ -24,5 +24,9 @@ module Charta
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