terraformer 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +18 -10
- data/Rakefile +3 -0
- data/lib/ext/array.rb +17 -0
- data/lib/terraformer/arcgis.rb +44 -29
- data/lib/terraformer/bounds.rb +18 -27
- data/lib/terraformer/convex_hull.rb +3 -1
- data/lib/terraformer/coordinate.rb +55 -20
- data/lib/terraformer/geometry/class_methods.rb +4 -0
- data/lib/terraformer/geometry.rb +28 -5
- data/lib/terraformer/line_string.rb +10 -0
- data/lib/terraformer/multi_line_string.rb +11 -12
- data/lib/terraformer/multi_point.rb +6 -0
- data/lib/terraformer/multi_polygon.rb +12 -2
- data/lib/terraformer/point.rb +9 -0
- data/lib/terraformer/polygon.rb +56 -10
- data/lib/terraformer/version.rb +1 -1
- data/lib/terraformer.rb +3 -2
- data/terraformer.gemspec +4 -4
- data/test/coordinate_spec.rb +0 -29
- data/test/multi_line_string_spec.rb +3 -7
- data/test/terraformer_spec.rb +1 -1
- metadata +8 -6
- data/lib/ext/enumerable.rb +0 -79
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8870157d8b1074418f97a8760cece253047829e0
|
4
|
+
data.tar.gz: de0f63a4d2a320e9e3c8a2c5b70025d2208a56fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 252e0d88535889c9bc9fe641bba534917288d77412b86db9de20a99fecfdd422ef245f6e2b8cbefc16cc69001726084d4633847d284aed1ab522bea4b2dc4954
|
7
|
+
data.tar.gz: c92efd04400e6a059fae22304b6d6f7b7344a79ffa530fd3a1dff08aa58e4690588c0c986bc765c7fb364ff255a09ebb755e150749f8f8cd4a32fa2e4b62158f
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -25,8 +25,8 @@ require 'terraformer'
|
|
25
25
|
|
26
26
|
##### Create a Terraformer primitive from GeoJSON
|
27
27
|
|
28
|
-
```
|
29
|
-
|
28
|
+
```ruby
|
29
|
+
polygon = Terraformer.parse '{
|
30
30
|
"type": "Polygon",
|
31
31
|
"coordinates": [
|
32
32
|
[
|
@@ -41,24 +41,32 @@ require 'terraformer'
|
|
41
41
|
]
|
42
42
|
}'
|
43
43
|
|
44
|
-
|
44
|
+
point = Terraformer.parse '{
|
45
45
|
"type": "Point",
|
46
46
|
"coordinates": [-122.66947746276854, 45.51775972687403]
|
47
47
|
}'
|
48
48
|
|
49
49
|
```
|
50
|
+
|
50
51
|
Now that you have a point and a polygon primitive you can use the primitive helper methods.
|
51
52
|
|
52
|
-
```
|
53
|
+
```ruby
|
53
54
|
# add a new vertex to our polygon
|
54
|
-
|
55
|
-
|
55
|
+
new_point = Terraformer::Point.new -122.6708507537842, 45.513188859735436
|
56
|
+
polygon.insert_vertex 2, new_point
|
56
57
|
```
|
58
|
+
|
57
59
|
You can also have Terraformer perform many geometric operations like convex hulls and bounding boxes.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# returns convex hull
|
63
|
+
convex_hull = polygon.convex_hull
|
64
|
+
|
65
|
+
point.within? convex_hull
|
66
|
+
=> true
|
67
|
+
|
68
|
+
# returns the bounding box
|
69
|
+
bounding_box = polygon.bbox
|
62
70
|
```
|
63
71
|
|
64
72
|
## Contributing
|
data/Rakefile
CHANGED
data/lib/ext/array.rb
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
class Array
|
2
2
|
|
3
|
+
def rotate_until &block
|
4
|
+
return if block[]
|
5
|
+
found = false
|
6
|
+
length.times do
|
7
|
+
push shift
|
8
|
+
if block[]
|
9
|
+
found = true
|
10
|
+
break
|
11
|
+
end
|
12
|
+
end
|
13
|
+
raise IndexError unless found
|
14
|
+
end
|
15
|
+
|
16
|
+
def rotate_until_first_equals obj
|
17
|
+
rotate_until { at(0) == obj }
|
18
|
+
end
|
19
|
+
|
3
20
|
def slice_exists? slice
|
4
21
|
start = slice.first
|
5
22
|
len = slice.size
|
data/lib/terraformer/arcgis.rb
CHANGED
@@ -98,53 +98,68 @@ module Terraformer
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
+
def parse_point arcgis
|
102
|
+
Coordinate.new(%w[x y z].map {|k| arcgis[k]}).to_point
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_points arcgis
|
106
|
+
require_array arcgis['points']
|
107
|
+
MultiPoint.new arcgis['points']
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_paths arcgis
|
111
|
+
require_array arcgis['paths']
|
112
|
+
if arcgis['paths'].length == 1
|
113
|
+
LineString.new arcgis['paths'][0]
|
114
|
+
else
|
115
|
+
MultiLineString.new arcgis['paths']
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_geometry arcgis, opts
|
120
|
+
if arcgis['compressedGeometry']
|
121
|
+
arcgis['geometry'] = {'paths' => [decompress_geometry(arcgis['compressedGeometry'])]}
|
122
|
+
end
|
123
|
+
|
124
|
+
o = Feature.new
|
125
|
+
o.geometry = parse arcgis['geometry'] if arcgis['geometry']
|
126
|
+
if attrs = arcgis['attributes']
|
127
|
+
o.properties = attrs.clone
|
128
|
+
if opts[:id_attribute] && o.properties[opts[:id_attribute]]
|
129
|
+
o.id = o.properties.delete opts[:id_attribute]
|
130
|
+
elsif o.properties[OBJECT_ID]
|
131
|
+
o.id = o.properties.delete OBJECT_ID
|
132
|
+
elsif o.properties['FID']
|
133
|
+
o.id = o.properties.delete 'FID'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
return o
|
138
|
+
end
|
139
|
+
|
101
140
|
def parse arcgis, opts = {}
|
102
141
|
arcgis = JSON.parse arcgis if String === arcgis
|
103
142
|
raise ArgumentError.new 'argument not hash nor json' unless Hash === arcgis
|
104
143
|
|
105
144
|
obj = case
|
106
145
|
when Numeric === arcgis['x'] && Numeric === arcgis['y']
|
107
|
-
|
146
|
+
parse_point arcgis
|
108
147
|
|
109
148
|
when arcgis['points']
|
110
|
-
|
111
|
-
MultiPoint.new arcgis['points']
|
149
|
+
parse_points arcgis
|
112
150
|
|
113
151
|
when arcgis['paths']
|
114
|
-
|
115
|
-
if arcgis['paths'].length == 1
|
116
|
-
LineString.new arcgis['paths'][0]
|
117
|
-
else
|
118
|
-
MultiLineString.new arcgis['paths']
|
119
|
-
end
|
152
|
+
parse_paths arcgis
|
120
153
|
|
121
154
|
when arcgis['rings']
|
122
155
|
convert_rings arcgis['rings']
|
123
156
|
|
124
157
|
when !(%w[compressedGeometry geometry attributes].map {|k| arcgis[k]}.empty?)
|
125
|
-
|
126
|
-
if arcgis['compressedGeometry']
|
127
|
-
arcgis['geometry'] = {'paths' => [decompress_geometry(arcgis['compressedGeometry'])]}
|
128
|
-
end
|
129
|
-
|
130
|
-
o = Feature.new
|
131
|
-
o.geometry = parse arcgis['geometry'] if arcgis['geometry']
|
132
|
-
if attrs = arcgis['attributes']
|
133
|
-
o.properties = attrs.clone
|
134
|
-
if opts[:id_attribute] and o.properties[opts[:id_attribute]]
|
135
|
-
o.id = o.properties.delete opts[:id_attribute]
|
136
|
-
elsif o.properties[OBJECT_ID]
|
137
|
-
o.id = o.properties.delete OBJECT_ID
|
138
|
-
elsif o.properties['FID']
|
139
|
-
o.id = o.properties.delete 'FID'
|
140
|
-
end
|
141
|
-
end
|
142
|
-
o
|
143
|
-
|
158
|
+
parse_geometry arcgis, opts
|
144
159
|
end
|
145
160
|
|
146
161
|
isr = arcgis['geometry'] ? arcgis['geometry']['spatialReference'] : arcgis['spatialReference']
|
147
|
-
if isr
|
162
|
+
if isr && Integer(isr['wkid']) == 102100
|
148
163
|
if Feature === obj
|
149
164
|
obj.geometry = obj.geometry.to_geographic
|
150
165
|
else
|
data/lib/terraformer/bounds.rb
CHANGED
@@ -7,25 +7,19 @@ module Terraformer
|
|
7
7
|
|
8
8
|
obj = Terraformer.parse obj unless Geometry === obj
|
9
9
|
|
10
|
-
bbox = case obj
|
11
|
-
when
|
10
|
+
bbox = case obj
|
11
|
+
when Point
|
12
12
|
[ obj.coordinates[0], obj.coordinates[1],
|
13
13
|
obj.coordinates[0], obj.coordinates[1] ]
|
14
|
-
when
|
14
|
+
when MultiPoint, LineString,
|
15
|
+
MultiLineString, Polygon,
|
16
|
+
MultiPolygon
|
15
17
|
bounds_for_array obj.coordinates
|
16
|
-
when
|
17
|
-
bounds_for_array obj.coordinates
|
18
|
-
when 'MultiLineString'
|
19
|
-
bounds_for_array obj.coordinates, 1
|
20
|
-
when 'Polygon'
|
21
|
-
bounds_for_array obj.coordinates, 1
|
22
|
-
when 'MultiPolygon'
|
23
|
-
bounds_for_array obj.coordinates, 2
|
24
|
-
when 'Feature'
|
18
|
+
when Feature
|
25
19
|
obj.geometry ? bounds(obj.geometry) : nil
|
26
|
-
when
|
20
|
+
when FeatureCollection
|
27
21
|
bounds_for_feature_collection obj
|
28
|
-
when
|
22
|
+
when GeometryCollection
|
29
23
|
bounds_for_geometry_collection obj
|
30
24
|
else
|
31
25
|
raise ArgumentError.new 'unknown type: ' + obj.type
|
@@ -45,22 +39,19 @@ module Terraformer
|
|
45
39
|
|
46
40
|
X1, Y1, X2, Y2 = 0, 1, 2, 3
|
47
41
|
|
48
|
-
def bounds_for_array array,
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
else
|
54
|
-
bbox = array.reduce box do |b, lonlat|
|
55
|
-
lon, lat = *lonlat
|
42
|
+
def bounds_for_array array, box = Array.new(4)
|
43
|
+
array.reduce box do |b, a|
|
44
|
+
case a
|
45
|
+
when Coordinate
|
56
46
|
set = ->(d, i, t){ b[i] = d if b[i].nil? or d.send(t, b[i])}
|
57
|
-
set[
|
58
|
-
set[
|
59
|
-
set[
|
60
|
-
set[
|
47
|
+
set[a[0], X1, :<]
|
48
|
+
set[a[0], X2, :>]
|
49
|
+
set[a[1], Y1, :<]
|
50
|
+
set[a[1], Y2, :>]
|
61
51
|
b
|
52
|
+
else
|
53
|
+
bounds_for_array a, box
|
62
54
|
end
|
63
|
-
bbox
|
64
55
|
end
|
65
56
|
end
|
66
57
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Terraformer
|
2
2
|
|
3
|
-
class Coordinate
|
3
|
+
class Coordinate
|
4
4
|
|
5
5
|
# http://en.wikipedia.org/wiki/Earth_radius#Mean_radius
|
6
6
|
#
|
@@ -8,14 +8,21 @@ module Terraformer
|
|
8
8
|
|
9
9
|
attr_accessor :crs
|
10
10
|
|
11
|
-
|
11
|
+
# array holding the numeric coordinate values
|
12
|
+
attr_accessor :ary
|
12
13
|
|
13
|
-
|
14
|
-
arys.map {|e| Coordinate.from_array e}
|
15
|
-
end
|
14
|
+
class << self
|
16
15
|
|
17
16
|
def from_array a
|
18
|
-
Coordinate
|
17
|
+
if Coordinate === a
|
18
|
+
a.dup
|
19
|
+
elsif Numeric === a[0]
|
20
|
+
Coordinate.new a
|
21
|
+
elsif Array === a
|
22
|
+
a.map {|e| Coordinate.from_array e }
|
23
|
+
else
|
24
|
+
raise ArgumentError
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
28
|
def big_decimal n
|
@@ -34,14 +41,21 @@ module Terraformer
|
|
34
41
|
end
|
35
42
|
|
36
43
|
def initialize _x, _y = nil, _z = nil
|
37
|
-
|
38
|
-
case
|
39
|
-
when Array
|
44
|
+
@ary = Array.new 3
|
45
|
+
case _x
|
46
|
+
when Array
|
40
47
|
raise ArgumentError if _y
|
41
48
|
self.x = _x[0]
|
42
49
|
self.y = _x[1]
|
43
50
|
self.z = _x[2] if _x[2]
|
44
|
-
|
51
|
+
|
52
|
+
when Coordinate
|
53
|
+
raise ArgumentError if _y
|
54
|
+
self.x = _x.x
|
55
|
+
self.y = _x.y
|
56
|
+
self.z = _x.z if _x.z
|
57
|
+
|
58
|
+
when Numeric, String
|
45
59
|
raise ArgumentError unless _y
|
46
60
|
self.x = _x
|
47
61
|
self.y = _y
|
@@ -51,32 +65,53 @@ module Terraformer
|
|
51
65
|
end
|
52
66
|
end
|
53
67
|
|
68
|
+
def dup
|
69
|
+
Coordinate.new @ary.dup
|
70
|
+
end
|
71
|
+
|
54
72
|
def x
|
55
|
-
|
73
|
+
@ary[0]
|
56
74
|
end
|
75
|
+
alias_method :lon, :x
|
57
76
|
|
58
77
|
def x= _x
|
59
|
-
|
78
|
+
@ary[0] = Coordinate.big_decimal _x
|
60
79
|
end
|
61
80
|
|
62
81
|
def y
|
63
|
-
|
82
|
+
@ary[1]
|
64
83
|
end
|
84
|
+
alias_method :lat, :y
|
65
85
|
|
66
86
|
def y= _y
|
67
|
-
|
87
|
+
@ary[1] = Coordinate.big_decimal _y
|
68
88
|
end
|
69
89
|
|
70
90
|
def z
|
71
|
-
|
91
|
+
@ary[2]
|
72
92
|
end
|
73
93
|
|
74
94
|
def z= _z
|
75
|
-
|
95
|
+
@ary[2] = Coordinate.big_decimal _z
|
96
|
+
end
|
97
|
+
|
98
|
+
def [] index
|
99
|
+
@ary[index]
|
100
|
+
end
|
101
|
+
|
102
|
+
def == other
|
103
|
+
case other
|
104
|
+
when Array
|
105
|
+
@ary == other
|
106
|
+
when Coordinate
|
107
|
+
@ary == other.ary
|
108
|
+
else
|
109
|
+
false
|
110
|
+
end
|
76
111
|
end
|
77
112
|
|
78
|
-
|
79
|
-
|
113
|
+
def inspect
|
114
|
+
"#<Terraformer::Coordinate lon=#{lon.to_s 'F'} lat=#{lat.to_s 'F'} #{z if z }>"
|
80
115
|
end
|
81
116
|
|
82
117
|
def to_geographic
|
@@ -101,11 +136,11 @@ module Terraformer
|
|
101
136
|
end
|
102
137
|
|
103
138
|
def to_s
|
104
|
-
|
139
|
+
@ary.compact.join ','
|
105
140
|
end
|
106
141
|
|
107
142
|
def to_json *args
|
108
|
-
compact.to_json(*args)
|
143
|
+
@ary.compact.to_json(*args)
|
109
144
|
end
|
110
145
|
|
111
146
|
def to_point
|
data/lib/terraformer/geometry.rb
CHANGED
@@ -13,7 +13,7 @@ module Terraformer
|
|
13
13
|
case
|
14
14
|
when args.length > 1
|
15
15
|
self.coordinates = Coordinate.from_array args
|
16
|
-
when Array === args[0]
|
16
|
+
when Array === args[0] || Coordinate === args[0]
|
17
17
|
self.coordinates = Coordinate.from_array args[0]
|
18
18
|
else
|
19
19
|
super *args do |arg|
|
@@ -22,6 +22,28 @@ module Terraformer
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
def each_coordinate &block
|
26
|
+
Geometry.iter_coordinate coordinates, :each, &block
|
27
|
+
end
|
28
|
+
|
29
|
+
def map_coordinate &block
|
30
|
+
Geometry.iter_coordinate coordinates, :map, &block
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.iter_coordinate obj, meth, &block
|
34
|
+
if Terraformer::Coordinate === obj
|
35
|
+
block.call obj
|
36
|
+
elsif Array === obj
|
37
|
+
obj.__send__ meth do |pair|
|
38
|
+
if Array === pair
|
39
|
+
Geometry.iter_coordinate pair, meth, &block
|
40
|
+
else
|
41
|
+
block.call pair
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
25
47
|
def to_hash *args
|
26
48
|
h = {
|
27
49
|
type: type,
|
@@ -33,11 +55,11 @@ module Terraformer
|
|
33
55
|
end
|
34
56
|
|
35
57
|
def to_mercator
|
36
|
-
self.class.new *
|
58
|
+
self.class.new *map_coordinate(&:to_mercator)
|
37
59
|
end
|
38
60
|
|
39
61
|
def to_geographic
|
40
|
-
self.class.new *
|
62
|
+
self.class.new *map_coordinate(&:to_geographic)
|
41
63
|
end
|
42
64
|
|
43
65
|
def to_feature
|
@@ -85,8 +107,9 @@ module Terraformer
|
|
85
107
|
raise ArgumentError.new "unsupported type: #{e.type rescue e.class}"
|
86
108
|
end
|
87
109
|
end
|
88
|
-
|
89
|
-
|
110
|
+
|
111
|
+
begin
|
112
|
+
return true if within? other or other.within? self
|
90
113
|
rescue ArgumentError
|
91
114
|
false
|
92
115
|
end
|
@@ -2,6 +2,16 @@ module Terraformer
|
|
2
2
|
|
3
3
|
class LineString < Geometry
|
4
4
|
|
5
|
+
def initialize *args
|
6
|
+
super *args
|
7
|
+
|
8
|
+
# must be an array of coordinates
|
9
|
+
unless Array === coordinates &&
|
10
|
+
Terraformer::Coordinate === coordinates[0]
|
11
|
+
raise ArgumentError.new 'invalid coordinates for Terraformer::LineString'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
5
15
|
def first_coordinate
|
6
16
|
coordinates[0]
|
7
17
|
end
|
@@ -3,16 +3,20 @@ module Terraformer
|
|
3
3
|
class MultiLineString < Geometry
|
4
4
|
|
5
5
|
def initialize *args
|
6
|
+
|
6
7
|
case
|
7
|
-
when Coordinate === args[0] # only one
|
8
|
-
self.coordinates = [Coordinate.from_array(args)]
|
9
|
-
when Array === args[0] # multiple?
|
10
|
-
self.coordinates = Coordinate.from args
|
11
8
|
when LineString === args[0]
|
12
9
|
self.coordinates = args.map &:coordinates
|
13
10
|
else
|
14
11
|
super *args
|
15
12
|
end
|
13
|
+
|
14
|
+
# must be an array of arrays of coordinates
|
15
|
+
unless Array === coordinates &&
|
16
|
+
Array === coordinates[0] &&
|
17
|
+
Terraformer::Coordinate === coordinates[0][0]
|
18
|
+
raise ArgumentError.new 'invalid coordinates for Terraformer::MultiLineString'
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
def first_coordinate
|
@@ -25,14 +29,9 @@ module Terraformer
|
|
25
29
|
|
26
30
|
def == obj
|
27
31
|
super obj do |o|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
lses.each_with_index do |ls, i|
|
32
|
-
equal = ls == olses[i]
|
33
|
-
break unless equal
|
34
|
-
end
|
35
|
-
equal
|
32
|
+
lses = line_strings.sort {|a,b| a.first_coordinate <=> b.first_coordinate }
|
33
|
+
olses = o.line_strings.sort {|a,b| a.first_coordinate <=> b.first_coordinate }
|
34
|
+
lses == olses
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
@@ -9,6 +9,12 @@ module Terraformer
|
|
9
9
|
else
|
10
10
|
super *args
|
11
11
|
end
|
12
|
+
|
13
|
+
# must be an array of coordinates
|
14
|
+
unless Array === coordinates &&
|
15
|
+
Terraformer::Coordinate === coordinates[0]
|
16
|
+
raise ArgumentError.new 'invalid coordinates for Terraformer::MultiPoint'
|
17
|
+
end
|
12
18
|
end
|
13
19
|
|
14
20
|
def first_coordinate
|
@@ -7,18 +7,28 @@ module Terraformer
|
|
7
7
|
|
8
8
|
# arg is an array of arrays of polygon, holes
|
9
9
|
when Array === args[0] && Array === args[0][0] && Array === args[0][0][0] && Array === args[0][0][0][0]
|
10
|
-
self.coordinates = Coordinate.
|
10
|
+
self.coordinates = Coordinate.from_array(*args)
|
11
11
|
|
12
12
|
when Coordinate === args[0] # only one
|
13
13
|
self.coordinates = [[Coordinate.from_array(args)]]
|
14
|
+
|
14
15
|
when Array === args[0] # multiple?
|
15
|
-
self.coordinates = [Coordinate.
|
16
|
+
self.coordinates = [Coordinate.from_array(args)]
|
17
|
+
|
16
18
|
when Polygon === args[0]
|
17
19
|
self.coordinates = args.map &:coordinates
|
18
20
|
|
19
21
|
else
|
20
22
|
super *args
|
21
23
|
end
|
24
|
+
|
25
|
+
# must be an array of arrays of arrays of coordinates (whew!)
|
26
|
+
unless Array === coordinates &&
|
27
|
+
Array === coordinates[0] &&
|
28
|
+
Array === coordinates[0][0] &&
|
29
|
+
Terraformer::Coordinate === coordinates[0][0][0]
|
30
|
+
raise ArgumentError.new 'invalid coordinates for Terraformer::MultiPolygon'
|
31
|
+
end
|
22
32
|
end
|
23
33
|
|
24
34
|
def first_coordinate
|
data/lib/terraformer/point.rb
CHANGED
@@ -2,6 +2,15 @@ module Terraformer
|
|
2
2
|
|
3
3
|
class Point < Geometry
|
4
4
|
|
5
|
+
def initialize *args
|
6
|
+
super
|
7
|
+
|
8
|
+
# must be a single point
|
9
|
+
unless Terraformer::Coordinate === coordinates
|
10
|
+
raise ArgumentError.new 'invalid coordinates for Terraformer::Point'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
5
14
|
def first_coordinate
|
6
15
|
coordinates
|
7
16
|
end
|
data/lib/terraformer/polygon.rb
CHANGED
@@ -3,24 +3,34 @@ module Terraformer
|
|
3
3
|
class Polygon < Geometry
|
4
4
|
|
5
5
|
def initialize *args
|
6
|
-
case
|
7
6
|
|
8
7
|
# each arg is a position of the polygon
|
9
|
-
|
8
|
+
if Coordinate === args[0] || (Array === args[0] && Numeric === args[0][0])
|
10
9
|
self.coordinates = [Coordinate.from_array(args)]
|
11
10
|
|
12
|
-
|
13
|
-
when Array === args[0] && Array === args[0][0] && Numeric === args[0][0][0]
|
14
|
-
self.coordinates = Coordinate.from args
|
11
|
+
elsif Array === args[0]
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
# each arg is an array of positions; first is polygon, rest are "holes"
|
14
|
+
if Coordinate === args[0][0] ||
|
15
|
+
Array === args[0][0] && Numeric === args[0][0][0]
|
16
|
+
self.coordinates = Coordinate.from_array args
|
17
|
+
|
18
|
+
# arg is an array of polygon, holes
|
19
|
+
elsif Array === args[0][0] && Array === args[0][0][0]
|
20
|
+
self.coordinates = Coordinate.from_array *args
|
21
|
+
end
|
19
22
|
|
20
23
|
else
|
21
24
|
super *args
|
22
25
|
end
|
23
26
|
|
27
|
+
# must be an array of arrays of coordinates
|
28
|
+
unless Array === coordinates &&
|
29
|
+
Array === coordinates[0] &&
|
30
|
+
Terraformer::Coordinate === coordinates[0][0]
|
31
|
+
raise ArgumentError.new 'invalid coordinates for Terraformer::Polygon'
|
32
|
+
end
|
33
|
+
|
24
34
|
if line_strings.map(&:linear_ring?).include? false
|
25
35
|
raise ArgumentError.new 'not linear ring'
|
26
36
|
end
|
@@ -40,7 +50,7 @@ module Terraformer
|
|
40
50
|
equal = true
|
41
51
|
|
42
52
|
# first check outer polygon
|
43
|
-
equal = self.coordinates[0]
|
53
|
+
equal = Polygon.polygonally_equal? self.coordinates[0], obj.coordinates[0]
|
44
54
|
|
45
55
|
# then inner polygons (holes)
|
46
56
|
#
|
@@ -51,7 +61,7 @@ module Terraformer
|
|
51
61
|
obj_holes = obj.coordinates[1..-1].sort
|
52
62
|
|
53
63
|
self_holes.each_with_index do |hole, idx|
|
54
|
-
equal =
|
64
|
+
equal = Polygon.polygonally_equal? hole, obj_holes[idx]
|
55
65
|
break unless equal
|
56
66
|
end
|
57
67
|
end
|
@@ -130,6 +140,42 @@ module Terraformer
|
|
130
140
|
coordinates[0].delete_at idx
|
131
141
|
end
|
132
142
|
|
143
|
+
|
144
|
+
def self.polygonally_equal? a, b
|
145
|
+
if Terraformer::Coordinate === a ||
|
146
|
+
Terraformer::Coordinate === b
|
147
|
+
return a == b
|
148
|
+
end
|
149
|
+
|
150
|
+
raise ArgumentError unless Enumerable === a
|
151
|
+
raise ArgumentError unless Enumerable === b
|
152
|
+
|
153
|
+
# polygons must be the same length
|
154
|
+
return false if a.length != b.length
|
155
|
+
|
156
|
+
# polygons must have the last element be a duplicate
|
157
|
+
# polygon-closing coordinate
|
158
|
+
return false if a[0] != a[-1]
|
159
|
+
return false if b[0] != b[-1]
|
160
|
+
|
161
|
+
equal = true
|
162
|
+
|
163
|
+
# clone so can pop/rotate
|
164
|
+
a = a.clone
|
165
|
+
b = b.clone
|
166
|
+
|
167
|
+
# pop to drop the duplicate, polygon-closing, coordinate
|
168
|
+
a.pop
|
169
|
+
b.pop
|
170
|
+
|
171
|
+
begin
|
172
|
+
b.rotate_until_first_equals a[0]
|
173
|
+
return a == b
|
174
|
+
rescue IndexError
|
175
|
+
return false
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
133
179
|
end
|
134
180
|
|
135
181
|
end
|
data/lib/terraformer/version.rb
CHANGED
data/lib/terraformer.rb
CHANGED
@@ -5,7 +5,6 @@ require 'bigdecimal/util'
|
|
5
5
|
require 'ext/array'
|
6
6
|
require 'ext/big_decimal'
|
7
7
|
require 'ext/big_math'
|
8
|
-
require 'ext/enumerable'
|
9
8
|
require 'forwardable'
|
10
9
|
|
11
10
|
# terraformer.rb - a toolkit for working with geojson in ruby
|
@@ -73,7 +72,9 @@ module Terraformer
|
|
73
72
|
# handles basic JSON parsing for terraformer object constructors.
|
74
73
|
#
|
75
74
|
def initialize *args
|
76
|
-
arg =
|
75
|
+
arg = args[0]
|
76
|
+
arg = JSON.parse(arg) if String === arg
|
77
|
+
|
77
78
|
raise ArgumentError.new "invalid argument(s): #{args}" unless Hash === arg
|
78
79
|
raise ArgumentError.new "invalid type: #{arg['type']}" unless arg['type'] == self.type
|
79
80
|
yield arg if block_given?
|
data/terraformer.gemspec
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
require File.expand_path('../lib/terraformer/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = ["Kenichi Nakamura"]
|
6
|
-
gem.email = ["kenichi.nakamura@gmail.com"]
|
7
|
-
gem.description = gem.summary = ""
|
8
|
-
gem.homepage = "https://github.com/
|
5
|
+
gem.authors = ["Kenichi Nakamura", "Davy Stevenson"]
|
6
|
+
gem.email = ["kenichi.nakamura@gmail.com", "davy.stevenson@gmail.com"]
|
7
|
+
gem.description = gem.summary = "toolkit for working with GeoJSON in pure Ruby"
|
8
|
+
gem.homepage = "https://github.com/kenichi/terraformer-ruby"
|
9
9
|
gem.files = `git ls-files | grep -Ev '^(myapp|examples)'`.split("\n")
|
10
10
|
gem.test_files = `git ls-files -- test/*`.split("\n")
|
11
11
|
gem.name = "terraformer"
|
data/test/coordinate_spec.rb
CHANGED
@@ -2,35 +2,6 @@ require_relative './helper'
|
|
2
2
|
|
3
3
|
describe Terraformer::Coordinate do
|
4
4
|
|
5
|
-
describe 'from' do
|
6
|
-
it 'should accept nested arrays' do
|
7
|
-
arys = [[100,0], [101,1], [102,2]]
|
8
|
-
c = Terraformer::Coordinate.from arys
|
9
|
-
|
10
|
-
c.class.must_equal Array
|
11
|
-
c[0].class.must_equal Terraformer::Coordinate
|
12
|
-
c[0].x.must_equal 100
|
13
|
-
c[0].y.must_equal 0
|
14
|
-
c[1].class.must_equal Terraformer::Coordinate
|
15
|
-
c[1].x.must_equal 101
|
16
|
-
c[1].y.must_equal 1
|
17
|
-
c[2].class.must_equal Terraformer::Coordinate
|
18
|
-
c[2].x.must_equal 102
|
19
|
-
c[2].y.must_equal 2
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'should accept double nested arrays' do
|
23
|
-
arys = [[[100,0], [101,1]], [[102,2], [103,3]]]
|
24
|
-
c = Terraformer::Coordinate.from arys
|
25
|
-
|
26
|
-
c.class.must_equal Array
|
27
|
-
c[0].class.must_equal Array
|
28
|
-
c[0][0].class.must_equal Terraformer::Coordinate
|
29
|
-
c[0][0].x.must_equal 100
|
30
|
-
c[0][0].y.must_equal 0
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
5
|
describe 'from_array' do
|
35
6
|
|
36
7
|
it 'should be called from Array.to_c' do
|
@@ -8,15 +8,13 @@ describe Terraformer::MultiLineString do
|
|
8
8
|
a = Terraformer::Coordinate.new -122.6764, 45.5165
|
9
9
|
b = a + [0.02, 0.02]
|
10
10
|
c = b + [0.1, -0.1]
|
11
|
-
mls = Terraformer::MultiLineString.new a, b, c
|
11
|
+
mls = Terraformer::MultiLineString.new [[a, b, c]]
|
12
12
|
mls.to_json.must_equal '{"type":"MultiLineString","coordinates":[[[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]]]}'
|
13
13
|
mls.must_be_valid_geojson
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'constructs from array - single line' do
|
17
|
-
|
18
|
-
# mls = Terraformer::MultiLineString.new [[[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]]]
|
19
|
-
mls = Terraformer::MultiLineString.new [[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]]
|
17
|
+
mls = Terraformer::MultiLineString.new [[[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]]]
|
20
18
|
mls.to_json.must_equal '{"type":"MultiLineString","coordinates":[[[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]]]}'
|
21
19
|
mls.must_be_valid_geojson
|
22
20
|
end
|
@@ -47,9 +45,7 @@ describe Terraformer::MultiLineString do
|
|
47
45
|
end
|
48
46
|
|
49
47
|
it 'constructs from array - multiple lines' do
|
50
|
-
|
51
|
-
# mls = Terraformer::MultiLineString.new [[[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]],[[-121.5564,46.4365],[-121.5364,46.4565],[-121.4364,46.3565]]]
|
52
|
-
mls = Terraformer::MultiLineString.new [[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]],[[-121.5564,46.4365],[-121.5364,46.4565],[-121.4364,46.3565]]
|
48
|
+
mls = Terraformer::MultiLineString.new [[[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]],[[-121.5564,46.4365],[-121.5364,46.4565],[-121.4364,46.3565]]]
|
53
49
|
mls.to_json.must_equal '{"type":"MultiLineString","coordinates":[[[-122.6764,45.5165],[-122.6564,45.5365],[-122.5564,45.4365]],[[-121.5564,46.4365],[-121.5364,46.4565],[-121.4364,46.3565]]]}'
|
54
50
|
mls.must_be_valid_geojson
|
55
51
|
end
|
data/test/terraformer_spec.rb
CHANGED
@@ -163,7 +163,7 @@ describe Terraformer do
|
|
163
163
|
a = d.split
|
164
164
|
a[1][a[3]..-1].length
|
165
165
|
}
|
166
|
-
c.
|
166
|
+
c.each_coordinate do |c|
|
167
167
|
splitter[c.x].must_be :<=, Terraformer::PRECISION
|
168
168
|
splitter[c.x].must_be :<=, Terraformer::PRECISION
|
169
169
|
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: terraformer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Nakamura
|
8
|
+
- Davy Stevenson
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
12
|
+
date: 2015-02-03 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: launchy
|
@@ -24,21 +25,22 @@ dependencies:
|
|
24
25
|
- - "~>"
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: '2.4'
|
27
|
-
description:
|
28
|
+
description: toolkit for working with GeoJSON in pure Ruby
|
28
29
|
email:
|
29
30
|
- kenichi.nakamura@gmail.com
|
31
|
+
- davy.stevenson@gmail.com
|
30
32
|
executables: []
|
31
33
|
extensions: []
|
32
34
|
extra_rdoc_files: []
|
33
35
|
files:
|
34
36
|
- ".gitignore"
|
37
|
+
- CHANGELOG.md
|
35
38
|
- Gemfile
|
36
39
|
- README.md
|
37
40
|
- Rakefile
|
38
41
|
- lib/ext/array.rb
|
39
42
|
- lib/ext/big_decimal.rb
|
40
43
|
- lib/ext/big_math.rb
|
41
|
-
- lib/ext/enumerable.rb
|
42
44
|
- lib/terraformer.rb
|
43
45
|
- lib/terraformer/arcgis.rb
|
44
46
|
- lib/terraformer/bounds.rb
|
@@ -82,7 +84,7 @@ files:
|
|
82
84
|
- test/polygon_spec.rb
|
83
85
|
- test/primitive_spec.rb
|
84
86
|
- test/terraformer_spec.rb
|
85
|
-
homepage: https://github.com/
|
87
|
+
homepage: https://github.com/kenichi/terraformer-ruby
|
86
88
|
licenses:
|
87
89
|
- apache
|
88
90
|
metadata: {}
|
@@ -105,7 +107,7 @@ rubyforge_project:
|
|
105
107
|
rubygems_version: 2.4.5
|
106
108
|
signing_key:
|
107
109
|
specification_version: 4
|
108
|
-
summary:
|
110
|
+
summary: toolkit for working with GeoJSON in pure Ruby
|
109
111
|
test_files:
|
110
112
|
- test/arcgis_spec.rb
|
111
113
|
- test/circle_spec.rb
|
data/lib/ext/enumerable.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
module Enumerable
|
2
|
-
|
3
|
-
def each_coordinate opts = {}, &block
|
4
|
-
iter_coordinate :each, opts, &block
|
5
|
-
end
|
6
|
-
|
7
|
-
def map_coordinate opts = {}, &block
|
8
|
-
iter_coordinate :map, opts, &block
|
9
|
-
end
|
10
|
-
alias_method :collect_coordinate, :map_coordinate
|
11
|
-
|
12
|
-
def map_coordinate! opts = {}, &block
|
13
|
-
iter_coordinate :map!, opts, &block
|
14
|
-
end
|
15
|
-
alias_method :collect_coordinate!, :map_coordinate!
|
16
|
-
|
17
|
-
def iter_coordinate meth, opts = {}, &block
|
18
|
-
opts[:recurse] = true if opts[:recurse].nil?
|
19
|
-
|
20
|
-
if Array === self and Numeric === self[0]
|
21
|
-
yield self
|
22
|
-
else
|
23
|
-
|
24
|
-
self.__send__ meth do |pair|
|
25
|
-
raise IndexError unless Array === pair
|
26
|
-
case pair[0]
|
27
|
-
when Numeric
|
28
|
-
yield pair
|
29
|
-
when Array
|
30
|
-
pair.iter_coordinate meth, opts, &block if opts[:recurse]
|
31
|
-
else
|
32
|
-
raise IndexError.new "#{pair[0]} is not a Numeric or Array type"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def rotate_until &block
|
39
|
-
return if block[]
|
40
|
-
found = false
|
41
|
-
length.times do
|
42
|
-
push shift
|
43
|
-
if block[]
|
44
|
-
found = true
|
45
|
-
break
|
46
|
-
end
|
47
|
-
end
|
48
|
-
raise IndexError unless found
|
49
|
-
end
|
50
|
-
|
51
|
-
def rotate_until_first_equals obj
|
52
|
-
rotate_until { at(0) == obj }
|
53
|
-
end
|
54
|
-
|
55
|
-
def polygonally_equal_to? obj
|
56
|
-
raise ArgumentError unless Enumerable === obj
|
57
|
-
return false if self.length != obj.length
|
58
|
-
|
59
|
-
equal = true
|
60
|
-
|
61
|
-
# clone so can pop/rotate
|
62
|
-
me = self.clone
|
63
|
-
obj = obj.clone
|
64
|
-
|
65
|
-
# pop to drop the duplicate, polygon-closing, coordinate
|
66
|
-
me.pop
|
67
|
-
obj.pop
|
68
|
-
|
69
|
-
begin
|
70
|
-
obj.rotate_until_first_equals me[0]
|
71
|
-
equal = me == obj
|
72
|
-
rescue IndexError
|
73
|
-
equal = false
|
74
|
-
end
|
75
|
-
|
76
|
-
equal
|
77
|
-
end
|
78
|
-
|
79
|
-
end
|