charta 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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