charta 0.1.0

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,30 @@
1
+ require 'nokogiri'
2
+
3
+ class GeojsonImport
4
+ # TODO: handle a File object instead of calling IO read/write directly
5
+ def initialize(data)
6
+ @shapes = nil
7
+ @xml = data
8
+ end
9
+
10
+ def valid?
11
+ shapes = JSON.parse(@xml)
12
+ ::Charta::GeoJSON.valid?(shapes)
13
+ end
14
+
15
+ def shapes(options = {})
16
+ options[:to] ||= :json
17
+
18
+ @shapes = JSON.parse(@xml)
19
+
20
+ if options[:to].equal? :json
21
+ @shapes = @shapes.to_json
22
+ elsif options[:to].equal? :string
23
+ @shapes = @shapes.to_s
24
+ end
25
+ end
26
+
27
+ def as_geojson
28
+ @shapes.to_json
29
+ end
30
+ end
@@ -0,0 +1,278 @@
1
+ require 'json'
2
+ require 'rgeo/geo_json'
3
+ require 'rgeo/svg' # integrated lib for now
4
+
5
+ module Charta
6
+ # Represents a Geometry with SRID
7
+ class Geometry
8
+ def initialize(ewkt)
9
+ @ewkt = ewkt
10
+ raise ArgumentError, 'Need EWKT to instantiate Geometry' if @ewkt.to_s =~ /\A[[:space:]]*\z/
11
+ end
12
+
13
+ def inspect
14
+ "<#{self.class.name}(#{to_ewkt})>"
15
+ end
16
+
17
+ # Returns the type of the geometry as a string. Example: point,
18
+ # multi_polygon, geometry_collection...
19
+ def type
20
+ Charta.underscore(feature.geometry_type.type_name).to_sym
21
+ end
22
+
23
+ # Returns the type of the geometry as a string. EG: 'ST_Linestring', 'ST_Polygon',
24
+ # 'ST_MultiPolygon' etc. This function differs from GeometryType(geometry)
25
+ # in the case of the string and ST in front that is returned, as well as the fact
26
+ # that it will not indicate whether the geometry is measured.
27
+ def collection?
28
+ feature.geometry_type == RGeo::Feature::GeometryCollection
29
+ end
30
+
31
+ # Return the spatial reference identifier for the ST_Geometry
32
+ def srid
33
+ feature.srid.to_i
34
+ end
35
+
36
+ # Returns the underlaying object managed by Charta: the RGeo feature
37
+ def to_rgeo
38
+ feature
39
+ end
40
+
41
+ # Returns the Well-Known Text (WKT) representation of the geometry/geography without SRID metadata
42
+ def to_text
43
+ feature.as_text
44
+ end
45
+ alias as_text to_text
46
+ alias to_wkt to_text
47
+
48
+ # Returns EWKT: WKT with its SRID
49
+ def to_ewkt
50
+ @ewkt.to_s
51
+ end
52
+ alias to_s to_ewkt
53
+
54
+ def ewkt
55
+ puts 'DEPRECATION WARNING: Charta::Geometry.ewkt is deprecated. Please use Charta::Geometry.to_ewkt instead'
56
+ to_ewkt
57
+ end
58
+
59
+ # Return the Well-Known Binary (WKB) representation of the geometry with SRID meta data.
60
+ def to_binary
61
+ generator = RGeo::WKRep::WKBGenerator.new(tag_format: :ewkbt, emit_ewkbt_srid: true)
62
+ generator.generate(feature)
63
+ end
64
+ alias to_ewkb to_binary
65
+
66
+ # Pas bien compris le fonctionnement
67
+ def to_svg(options = {})
68
+ svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"'
69
+ { preserve_aspect_ratio: 'xMidYMid meet',
70
+ width: 180, height: 180,
71
+ view_box: bounding_box.svg_view_box.join(' ') }.merge(options).each do |attr, value|
72
+ svg << " #{Charta.camelcase(attr.to_s, :lower)}=\"#{value}\""
73
+ end
74
+ svg << "><path d=\"#{to_svg_path}\"/></svg>"
75
+ svg
76
+ end
77
+
78
+ # Return the geometry as Scalar Vector Graphics (SVG) path data.
79
+ def to_svg_path
80
+ RGeo::SVG.encode(feature)
81
+ end
82
+
83
+ # Return the geometry as a Geometry Javascript Object Notation (GeoJSON) element.
84
+ def to_geojson
85
+ to_json_object.to_json
86
+ end
87
+ alias to_json to_geojson
88
+
89
+ # Returns object in JSON (Hash)
90
+ def to_json_object
91
+ RGeo::GeoJSON.encode(feature)
92
+ end
93
+
94
+ # Test if the other measure is equal to self
95
+ def ==(other)
96
+ other_geometry = Charta.new_geometry(other).transform(srid)
97
+ return true if empty? && other_geometry.empty?
98
+ return inspect == other_geometry.inspect if collection? && other_geometry.collection?
99
+ feature.equals?(other_geometry.feature)
100
+ end
101
+
102
+ # Test if the other measure is equal to self
103
+ def !=(other)
104
+ other_geometry = Charta.new_geometry(other).transform(srid)
105
+ return true if empty? && other_geometry.empty?
106
+ return inspect == other_geometry.inspect if collection? && other_geometry.collection?
107
+ !feature.equals?(other_geometry.feature)
108
+ end
109
+
110
+ # Returns true if Geometry is a Surface
111
+ def surface?
112
+ [RGeo::Feature::Polygon, RGeo::Feature::MultiPolygon].include? feature.geometry_type
113
+ end
114
+
115
+ # Returns area in unit corresponding to the SRS
116
+ def area
117
+ surface? ? feature.area : 0
118
+ end
119
+
120
+ # Returns true if this Geometry is an empty geometrycollection, polygon,
121
+ # point etc.
122
+ def empty?
123
+ feature.is_empty?
124
+ end
125
+ alias blank? empty?
126
+
127
+ # Computes the geometric center of a geometry, or equivalently, the center
128
+ # of mass of the geometry as a POINT.
129
+ def centroid
130
+ surface? ? feature.centroid : nil
131
+ end
132
+
133
+ # Returns a POINT guaranteed to lie on the surface.
134
+ def point_on_surface
135
+ surface? ? feature.point_on_surface : nil
136
+ end
137
+
138
+ def convert_to(new_type)
139
+ if new_type == type
140
+ self
141
+ elsif new_type == :multi_point
142
+ flatten_multi(:point)
143
+ elsif new_type == :multi_line_string
144
+ flatten_multi(:line_string)
145
+ elsif new_type == :multi_polygon
146
+ flatten_multi(:polygon)
147
+ end
148
+ end
149
+
150
+ def flatten_multi(as_type)
151
+ items = []
152
+ as_multi_type = "multi_#{as_type}".to_sym
153
+ if type == as_type
154
+ items << feature
155
+ elsif is_a? :geometry_collection
156
+ feature.each do |geom|
157
+ type_name = Charta.underscore(geom.geometry_type.type_name).to_sym
158
+ if type_name == as_type
159
+ items << geom
160
+ elsif type_name == as_multi_type
161
+ geom.each do |item|
162
+ items << item
163
+ end
164
+ end
165
+ end
166
+ end
167
+ Charta.new_geometry(feature.factory.send(as_multi_type, items))
168
+ end
169
+
170
+ # Returns a new geometry with the coordinates converted into the new SRS
171
+ def transform(new_srid)
172
+ return self if new_srid == srid
173
+ raise 'Proj is not supported. Cannot tranform' unless RGeo::CoordSys::Proj4.supported?
174
+ new_srid = Charta::SRS[new_srid] || new_srid
175
+ database = self.class.srs_database
176
+ new_proj_entry = database.get(new_srid)
177
+ raise "Cannot find proj for SRID: #{new_srid}" if new_proj_entry.nil?
178
+ new_feature = RGeo::CoordSys::Proj4.transform(
179
+ database.get(srid).proj4,
180
+ feature,
181
+ new_proj_entry.proj4,
182
+ self.class.factory(new_srid)
183
+ )
184
+ generator = RGeo::WKRep::WKTGenerator.new(tag_format: :ewkt, emit_ewkt_srid: true)
185
+ self.class.new(generator.generate(new_feature))
186
+ end
187
+
188
+ # Produces buffer
189
+ def buffer(radius)
190
+ feature.buffer(radius)
191
+ end
192
+
193
+ def merge(other)
194
+ other_geometry = Charta.new_geometry(other).transform(srid)
195
+ feature.union(other_geometry.feature)
196
+ end
197
+ alias + merge
198
+
199
+ def intersection(other)
200
+ other_geometry = Charta.new_geometry(other).transform(srid)
201
+ feature.intersection(other_geometry.feature)
202
+ end
203
+
204
+ def difference(other)
205
+ other_geometry = Charta.new_geometry(other).transform(srid)
206
+ feature.difference(other_geometry.feature)
207
+ end
208
+ alias - difference
209
+
210
+ def bounding_box
211
+ unless defined? @bounding_box
212
+ bbox = RGeo::Cartesian::BoundingBox.create_from_geometry(feature)
213
+ instance_variable_set('@x_min', bbox.min_x || 0)
214
+ instance_variable_set('@y_min', bbox.min_y || 0)
215
+ instance_variable_set('@x_max', bbox.max_x || 0)
216
+ instance_variable_set('@y_max', bbox.max_y || 0)
217
+ @bounding_box = BoundingBox.new(@y_min, @x_min, @y_max, @x_max)
218
+ end
219
+ @bounding_box
220
+ end
221
+
222
+ %i[x_min y_min x_max y_max].each do |name|
223
+ define_method name do
224
+ bounding_box.send(name)
225
+ end
226
+ end
227
+
228
+ def find_srid(name_or_srid)
229
+ Charta.find_srid(name_or_srid)
230
+ end
231
+
232
+ def feature
233
+ self.class.feature(@ewkt)
234
+ end
235
+
236
+ class << self
237
+ def srs_database
238
+ @srs_database ||= RGeo::CoordSys::SRSDatabase::Proj4Data.new('epsg', authority: 'EPSG', cache: true)
239
+ end
240
+
241
+ def factory(srid = 4326)
242
+ RGeo::Geos.factory(
243
+ # srs_database: srs_database,
244
+ srid: srid,
245
+ # has_z_coordinate: true,
246
+ wkt_generator: {
247
+ type_format: :ewkt,
248
+ emit_ewkt_srid: true,
249
+ convert_case: :upper
250
+ },
251
+ wkt_parser: {
252
+ support_ewkt: true
253
+ },
254
+ wkb_generator: {
255
+ type_format: :ewkb,
256
+ emit_ewkb_srid: true,
257
+ hex_format: true
258
+ },
259
+ wkb_parser: {
260
+ support_ewkb: true
261
+ }
262
+ )
263
+ end
264
+
265
+ def feature(ewkt)
266
+ # Cleans empty geometries
267
+ ewkt.gsub!(/(GEOMETRYCOLLECTION|GEOMETRY|((MULTI)?(POINT|LINESTRING|POLYGON)))\(\)/, '\1 EMPTY')
268
+ srs = ewkt.split(/[\=\;]+/)[0..1]
269
+ srid = nil
270
+ srid = srs[1] if srs[0] =~ /srid/i
271
+ srid ||= 4326
272
+ factory(srid).parse_wkt(ewkt)
273
+ rescue RGeo::Error::ParseError => e
274
+ raise "Invalid EWKT (#{e.class.name}: #{e.message}): #{ewkt}"
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,9 @@
1
+ module Charta
2
+ # Represent a Geometry with contains other geometries
3
+ class GeometryCollection < Geometry
4
+ def self.empty(srid = nil)
5
+ srid = Charta.find_srid(srid.nil? ? :WGS84 : srid)
6
+ new("SRID=#{srid};GEOMETRYCOLLECTION EMPTY")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,140 @@
1
+ require 'nokogiri'
2
+
3
+ module Charta
4
+ # Represents a Geometry with SRID
5
+ class GML
6
+ attr_reader :srid
7
+
8
+ TAGS = %w[Point LineString Polygon MultiGeometry].freeze
9
+ OGR_PREFIX = 'ogr'.freeze
10
+ GML_PREFIX = 'gml'.freeze
11
+ NS = {
12
+ gml: 'http://www.opengis.net/gml',
13
+ ogr: 'http://ogr.maptools.org/'
14
+ }.freeze
15
+
16
+ def initialize(data, srid = :WGS84)
17
+ srid ||= :WGS84
18
+ @gml = if data.is_a? String
19
+
20
+ Nokogiri::XML(data.to_s.split.join(' ')) do |config|
21
+ config.options = Nokogiri::XML::ParseOptions::NOBLANKS
22
+ end
23
+
24
+ else
25
+ # Nokogiri::XML::Document expected
26
+ data
27
+ end
28
+ up = false
29
+ # ensure namespaces are defined
30
+ begin
31
+ @gml.root.add_namespace_definition('xmlns', '')
32
+ NS.each do |k, v|
33
+ if @gml.xpath("//@*[xmlns:#{k}]").empty?
34
+ @gml.root.namespace_definitions << @gml.root.add_namespace_definition(k.to_s, v)
35
+ up = true
36
+ end
37
+ end
38
+ rescue
39
+ false
40
+ end
41
+
42
+ @gml = Nokogiri::XML(@gml.to_xml) if up
43
+
44
+ boundaries = @gml.css("#{GML_PREFIX}|boundedBy")
45
+ unless boundaries.nil?
46
+ boundaries.each do |node|
47
+ srid = Charta.find_srid(node['srsName']) unless node['srsName'].nil?
48
+ end
49
+ end
50
+
51
+ @srid = Charta.find_srid(srid)
52
+ end
53
+
54
+ def to_ewkt
55
+ "SRID=#{@srid};" + self.class.document_to_ewkt(@gml, @srid)
56
+ end
57
+
58
+ def valid?
59
+ to_ewkt
60
+ true
61
+ end
62
+
63
+ class << self
64
+ # Test is given data is a valid GML
65
+ def valid?(data, srid = :WGS84)
66
+ new(data, srid).valid?
67
+ end
68
+
69
+ def object_to_ewkt(fragment, srid)
70
+ send("#{Charta.underscore(fragment.name)}_to_ewkt", fragment, srid)
71
+ end
72
+
73
+ def document_to_ewkt(gml, srid)
74
+ # whole document
75
+ if gml.css("#{OGR_PREFIX}|FeatureCollection").nil? || gml.css("#{GML_PREFIX}|featureMember").nil?
76
+ # fragment
77
+ if gml.root.name && TAGS.include?(gml.root.name)
78
+ object_to_ewkt(gml.root, srid)
79
+ else
80
+ 'GEOMETRYCOLLECTION EMPTY'
81
+ end
82
+ else
83
+ 'GEOMETRYCOLLECTION(' + gml.css("#{GML_PREFIX}|featureMember").collect do |feature|
84
+ TAGS.collect do |tag|
85
+ next if feature.css("#{GML_PREFIX}|#{tag}").empty?
86
+ feature.css("#{GML_PREFIX}|#{tag}").collect do |fragment|
87
+ object_to_ewkt(fragment, srid)
88
+ end.compact.join(', ')
89
+ end.compact.join(', ')
90
+ end.compact.join(', ') + ')'
91
+ end
92
+ end
93
+ alias geometry_collection_to_ewkt document_to_ewkt
94
+
95
+ def transform(data, from_srid, to_srid)
96
+ Charta.new_geometry(data, from_srid).transform(to_srid).to_text
97
+ end
98
+
99
+ def polygon_to_ewkt(gml, srid)
100
+ return 'POLYGON EMPTY' if gml.css("#{GML_PREFIX}|coordinates").nil?
101
+
102
+ wkt = 'POLYGON(' + %w[outerBoundaryIs innerBoundaryIs].collect do |boundary|
103
+ next if gml.css("#{GML_PREFIX}|#{boundary}").empty?
104
+ 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(', ') + ')'
106
+ end.join(', ')
107
+ end.compact.join(', ') + ')'
108
+
109
+ unless gml['srsName'].nil? || Charta.find_srid(gml['srsName']).to_s == srid.to_s
110
+ wkt = transform(wkt, Charta.find_srid(gml['srsName']), srid)
111
+ end
112
+
113
+ wkt
114
+ end
115
+
116
+ def point_to_ewkt(gml, srid)
117
+ return 'POINT EMPTY' if gml.css("#{GML_PREFIX}|coordinates").nil?
118
+ wkt = 'POINT(' + gml.css("#{GML_PREFIX}|coordinates").collect { |coords| coords.content.split ',' }.flatten.join(' ') + ')'
119
+
120
+ unless gml['srsName'].nil? || Charta.find_srid(gml['srsName']).to_s == srid.to_s
121
+ wkt = transform(wkt, Charta.find_srid(gml['srsName']), srid)
122
+ end
123
+
124
+ wkt
125
+ end
126
+
127
+ def line_string_to_ewkt(gml, srid)
128
+ return 'LINESTRING EMPTY' if gml.css("#{GML_PREFIX}|coordinates").nil?
129
+
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(', ') + ')'
131
+
132
+ unless gml['srsName'].nil? || Charta.find_srid(gml['srsName']).to_s == srid.to_s
133
+ wkt = transform(wkt, Charta.find_srid(gml['srsName']), srid)
134
+ end
135
+
136
+ wkt
137
+ end
138
+ end
139
+ end
140
+ end