border_patrol 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.
- data/.gitignore +4 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +31 -0
- data/README.markdown +40 -0
- data/Rakefile +15 -0
- data/border_patrol.gemspec +28 -0
- data/lib/VERSION +1 -0
- data/lib/border_patrol.rb +28 -0
- data/lib/border_patrol/polygon.rb +73 -0
- data/lib/border_patrol/region.rb +12 -0
- data/spec/lib/border_patrol/polygon_spec.rb +139 -0
- data/spec/lib/border_patrol/region_spec.rb +46 -0
- data/spec/lib/border_patrol_spec.rb +60 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/colorado-test.kml +37 -0
- data/spec/support/formatters/compact_progress_bar_formatter.rb +133 -0
- data/spec/support/multi-polygon-test.kml +100 -0
- metadata +154 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
---
|
2
|
+
dependencies:
|
3
|
+
rake:
|
4
|
+
group:
|
5
|
+
- :development
|
6
|
+
version: = 0.8.7
|
7
|
+
rspec:
|
8
|
+
group:
|
9
|
+
- :development
|
10
|
+
version: = 1.3.0
|
11
|
+
progressbar:
|
12
|
+
group:
|
13
|
+
- :development
|
14
|
+
version: ">= 0"
|
15
|
+
nokogiri:
|
16
|
+
group:
|
17
|
+
- :default
|
18
|
+
version: = 1.4.3.1
|
19
|
+
specs:
|
20
|
+
- rake:
|
21
|
+
version: 0.8.7
|
22
|
+
- nokogiri:
|
23
|
+
version: 1.4.3.1
|
24
|
+
- progressbar:
|
25
|
+
version: 0.9.0
|
26
|
+
- rspec:
|
27
|
+
version: 1.3.0
|
28
|
+
hash: cbbadd85a1348941a32f127e7ae81222e0cfb8bc
|
29
|
+
sources:
|
30
|
+
- Rubygems:
|
31
|
+
uri: http://gemcutter.org
|
data/README.markdown
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# BorderPatrol
|
2
|
+
|
3
|
+
BorderPatrol lets you import a KML file and then check if points are inside or outside the polygons the file defines.
|
4
|
+
|
5
|
+
The KML file may have multiple polygons defined, google maps is a good source.
|
6
|
+
|
7
|
+
## Examples
|
8
|
+
|
9
|
+
An example KML file can be found here:
|
10
|
+
http://maps.google.com/maps/ms?ie=UTF8&hl=en&msa=0&ll=38.814031,-103.743896&spn=9.600749,16.248779&z=7&msid=110523771099674876521.00049301d20252132a92c&output=kml
|
11
|
+
|
12
|
+
To test if a point is in the region you can either pass a class that responds to `x` and `y` (like the provided BorderPatrol::Point class) or just pass a longitude latitude pair.
|
13
|
+
|
14
|
+
region = BorderPatrol.parse_kml(File.read('spec/support/colorado-test.kml'))
|
15
|
+
denver = BorderPatrol::Point.new(-105, 39.75)
|
16
|
+
region.contains_point?(denver) # true
|
17
|
+
region.contains_point?(-105, 39.75) # also true!
|
18
|
+
san_francisco = BorderPatrol::Point.new(-122.5, 37.75)
|
19
|
+
region.contains_point?(san_francisco) # false
|
20
|
+
region.contains_point?(-122.5, 37.75) # also false!
|
21
|
+
|
22
|
+
If you want to use your own point class, just define `x` and `y` as methods that correspond to `longitude` and `latitude`.
|
23
|
+
|
24
|
+
## Pro Tip
|
25
|
+
|
26
|
+
You can make KML files easily on Google Maps by clicking "My Maps", drawing shapes and saving the map. Just copy the share link and add "&output=kml" to download the file.g
|
27
|
+
|
28
|
+
## Dependencies
|
29
|
+
|
30
|
+
* Nokogiri
|
31
|
+
|
32
|
+
## Known Issues
|
33
|
+
|
34
|
+
Polygons across the international date line don't work.
|
35
|
+
|
36
|
+
## Acknowledgements
|
37
|
+
|
38
|
+
http://jakescruggs.blogspot.com/2009/07/point-inside-polygon-in-ruby.html for evaluating the algorithm.
|
39
|
+
|
40
|
+
http://github.com/nofxx/georuby/ for providing the bounding box code.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'rake'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
|
9
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
10
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
spec.libs << 'spec'
|
12
|
+
spec.spec_opts = ["--options", "spec/spec.opts"]
|
13
|
+
end
|
14
|
+
|
15
|
+
task :default => :spec
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "border_patrol"
|
7
|
+
s.version = File.read("lib/VERSION").strip
|
8
|
+
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
s.rubygems_version = "1.3.7"
|
11
|
+
|
12
|
+
s.authors = ["Zach Brock", "Matt Wilson"]
|
13
|
+
s.email = "eng@squareup.com"
|
14
|
+
|
15
|
+
s.date = "2010-10-20"
|
16
|
+
s.description = "Lets you import a KML file and then check if points are inside or outside the polygons the file defines."
|
17
|
+
s.summary = "Import and query KML regions"
|
18
|
+
s.homepage = "http://github.com/square/border_patrol"
|
19
|
+
|
20
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
21
|
+
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
root_files = %w(border_patrol.gemspec Rakefile README.markdown .gitignore Gemfile Gemfile.lock)
|
24
|
+
s.files = Dir['{lib,spec}/**/*'] + root_files
|
25
|
+
s.test_files = Dir['spec/**/*']
|
26
|
+
|
27
|
+
s.add_bundler_dependencies
|
28
|
+
end
|
data/lib/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BorderPatrol
|
2
|
+
class InsufficientPointsToActuallyFormAPolygonError < ArgumentError; end
|
3
|
+
class Point < Struct.new(:x, :y); end
|
4
|
+
|
5
|
+
def self.parse_kml(string)
|
6
|
+
doc = Nokogiri::XML(string)
|
7
|
+
polygons = doc.xpath('//kml:Polygon', 'kml' => 'http://earth.google.com/kml/2.2').map do |polygon_kml|
|
8
|
+
parse_kml_polygon_data(polygon_kml.to_s)
|
9
|
+
end
|
10
|
+
BorderPatrol::Region.new(polygons)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def self.parse_kml_polygon_data(string)
|
15
|
+
doc = Nokogiri::XML(string)
|
16
|
+
coordinates = doc.xpath("//coordinates").text.strip.split("\n")
|
17
|
+
points = coordinates.map do |coord|
|
18
|
+
x, y, z = coord.strip.split(',')
|
19
|
+
BorderPatrol::Point.new(x.to_f, y.to_f)
|
20
|
+
end
|
21
|
+
BorderPatrol::Polygon.new(points)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'set'
|
26
|
+
require 'nokogiri'
|
27
|
+
require 'border_patrol/polygon.rb'
|
28
|
+
require 'border_patrol/region.rb'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module BorderPatrol
|
2
|
+
class Polygon < Array
|
3
|
+
def initialize(* args)
|
4
|
+
args.flatten!
|
5
|
+
args.uniq!
|
6
|
+
raise InsufficientPointsToActuallyFormAPolygonError unless args.size > 2
|
7
|
+
super(args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
# Do we have the right number of points?
|
12
|
+
return false unless other.size == size
|
13
|
+
|
14
|
+
# Are the points in the right order?
|
15
|
+
first, second = self.first(2)
|
16
|
+
index = other.index(first)
|
17
|
+
return false unless index
|
18
|
+
direction = (other[index-1] == second) ? -1 : 1
|
19
|
+
# Check if the two polygons have the same edges and the same points
|
20
|
+
# i.e. [point1, point2, point3] is the same as [point2, point3, point1] is the same as [point3, point2, point1]
|
21
|
+
each do |i|
|
22
|
+
return false unless i == other[index]
|
23
|
+
index = index + direction
|
24
|
+
index = 0 if index == size
|
25
|
+
end
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Quick and dirty hash function
|
30
|
+
def hash
|
31
|
+
inject(0) { |sum, point| sum += point.x + point.y }
|
32
|
+
end
|
33
|
+
|
34
|
+
def contains_point?(point)
|
35
|
+
return false unless inside_bounding_box?(point)
|
36
|
+
c = false
|
37
|
+
i = -1
|
38
|
+
j = self.size - 1
|
39
|
+
while (i += 1) < self.size
|
40
|
+
if ((self[i].y <= point.y && point.y < self[j].y) ||
|
41
|
+
(self[j].y <= point.y && point.y < self[i].y))
|
42
|
+
if (point.x < (self[j].x - self[i].x) * (point.y - self[i].y) /
|
43
|
+
(self[j].y - self[i].y) + self[i].x)
|
44
|
+
c = !c
|
45
|
+
end
|
46
|
+
end
|
47
|
+
j = i
|
48
|
+
end
|
49
|
+
return c
|
50
|
+
end
|
51
|
+
|
52
|
+
def inside_bounding_box?(point)
|
53
|
+
bb_point_1, bb_point_2 = bounding_box
|
54
|
+
max_x = [bb_point_1.x, bb_point_2.x].max
|
55
|
+
max_y = [bb_point_1.y, bb_point_2.y].max
|
56
|
+
min_x = [bb_point_1.x, bb_point_2.x].min
|
57
|
+
min_y = [bb_point_1.y, bb_point_2.y].min
|
58
|
+
|
59
|
+
!(point.x < min_x || point.x > max_x || point.y < min_y || point.y > max_y)
|
60
|
+
end
|
61
|
+
|
62
|
+
def bounding_box
|
63
|
+
max_x, min_x, max_y, min_y = -Float::MAX, Float::MAX, -Float::MAX, Float::MAX
|
64
|
+
each do |point|
|
65
|
+
max_y = point.y if point.y > max_y
|
66
|
+
min_y = point.y if point.y < min_y
|
67
|
+
max_x = point.x if point.x > max_x
|
68
|
+
min_x = point.x if point.x < min_x
|
69
|
+
end
|
70
|
+
[Point.new(min_x, max_y), Point.new(max_x, min_y)]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module BorderPatrol
|
2
|
+
class Region < Set
|
3
|
+
def contains_point?(*point)
|
4
|
+
point = case point.length
|
5
|
+
when 1 then point.first
|
6
|
+
when 2 then BorderPatrol::Point.new(point[0],point[1])
|
7
|
+
else raise ArgumentError, "#{point} is invalid. Arguments can either be an object, or a longitude,lattitude pair."
|
8
|
+
end
|
9
|
+
any? { |polygon| polygon.contains_point?(point) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BorderPatrol::Polygon do
|
4
|
+
describe "==" do
|
5
|
+
it "is true if polygons are congruent" do
|
6
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0)]
|
7
|
+
poly1 = BorderPatrol::Polygon.new(points)
|
8
|
+
poly2 = BorderPatrol::Polygon.new(points.unshift(points.pop))
|
9
|
+
|
10
|
+
poly1.should == poly2
|
11
|
+
poly2.should == poly1
|
12
|
+
poly3 = BorderPatrol::Polygon.new(points.reverse)
|
13
|
+
poly1.should == poly3
|
14
|
+
poly3.should == poly1
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
it "cares about order of points" do
|
19
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(5,5), BorderPatrol::Point.new(0, 0)]
|
20
|
+
poly1 = BorderPatrol::Polygon.new(points)
|
21
|
+
points = [BorderPatrol::Point.new(5,5), BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(0, 0), BorderPatrol::Point.new(3, 4)]
|
22
|
+
poly2 = BorderPatrol::Polygon.new(points)
|
23
|
+
|
24
|
+
poly1.should_not == poly2
|
25
|
+
poly2.should_not == poly1
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is false if one polygon is a subset" do
|
30
|
+
poly1 = BorderPatrol::Polygon.new(BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0))
|
31
|
+
poly2 = BorderPatrol::Polygon.new(BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0), BorderPatrol::Point.new(4, 4))
|
32
|
+
poly2.should_not == poly1
|
33
|
+
poly1.should_not == poly2
|
34
|
+
end
|
35
|
+
|
36
|
+
it "is false if the polygons are not congruent" do
|
37
|
+
poly1 = BorderPatrol::Polygon.new(BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0))
|
38
|
+
poly2 = BorderPatrol::Polygon.new(BorderPatrol::Point.new(2, 1), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0))
|
39
|
+
poly2.should_not == poly1
|
40
|
+
poly1.should_not == poly2
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#initialize" do
|
45
|
+
it "stores a list of points" do
|
46
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0)]
|
47
|
+
polygon = BorderPatrol::Polygon.new(points)
|
48
|
+
points.each do |point|
|
49
|
+
polygon.should include point
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "can be instantiated with a arbitrary argument list" do
|
54
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0)]
|
55
|
+
poly1 = BorderPatrol::Polygon.new(*points)
|
56
|
+
poly2 = BorderPatrol::Polygon.new(points)
|
57
|
+
poly1.should == poly2
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises if less than 3 points are given" do
|
61
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(2, 3)]
|
62
|
+
expect { BorderPatrol::Polygon.new(points) }.to raise_exception(BorderPatrol::InsufficientPointsToActuallyFormAPolygonError)
|
63
|
+
points = [BorderPatrol::Point.new(1, 2)]
|
64
|
+
expect { BorderPatrol::Polygon.new(points) }.to raise_exception(BorderPatrol::InsufficientPointsToActuallyFormAPolygonError)
|
65
|
+
points = []
|
66
|
+
expect { BorderPatrol::Polygon.new(points) }.to raise_exception(BorderPatrol::InsufficientPointsToActuallyFormAPolygonError)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "doesn't store duplicated points" do
|
70
|
+
points = [BorderPatrol::Point.new(1, 2), BorderPatrol::Point.new(3, 4), BorderPatrol::Point.new(0, 0)]
|
71
|
+
duplicate_point = [BorderPatrol::Point.new(1, 2)]
|
72
|
+
polygon = BorderPatrol::Polygon.new(points + duplicate_point)
|
73
|
+
polygon.size.should == 3
|
74
|
+
points.each do |point|
|
75
|
+
polygon.should include point
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#bounding_box" do
|
81
|
+
it "returns the (max top, max left), (max bottom, max right) as points" do
|
82
|
+
points = [BorderPatrol::Point.new(-1, 3), BorderPatrol::Point.new(4, -3), BorderPatrol::Point.new(10, 4), BorderPatrol::Point.new(0, 12)]
|
83
|
+
polygon = BorderPatrol::Polygon.new(points)
|
84
|
+
polygon.bounding_box.should == [BorderPatrol::Point.new(-1, 12), BorderPatrol::Point.new(10, -3)]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#contains_point?" do
|
89
|
+
before do
|
90
|
+
points = [BorderPatrol::Point.new(-10, 0), BorderPatrol::Point.new(10, 0), BorderPatrol::Point.new(0, 10)]
|
91
|
+
@polygon = BorderPatrol::Polygon.new(points)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "is true if the point is in the polygon" do
|
95
|
+
@polygon.contains_point?(BorderPatrol::Point.new(0.5, 0.5)).should be_true
|
96
|
+
@polygon.contains_point?(BorderPatrol::Point.new(0, 5)).should be_true
|
97
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-1, 3)).should be_true
|
98
|
+
end
|
99
|
+
|
100
|
+
it "does not include points on the lines with slopes between vertices" do
|
101
|
+
@polygon.contains_point?(BorderPatrol::Point.new(5.0, 5.0)).should be_false
|
102
|
+
@polygon.contains_point?(BorderPatrol::Point.new(4.999999, 4.9999999)).should be_true
|
103
|
+
@polygon.contains_point?(BorderPatrol::Point.new(0, 0)).should be_true
|
104
|
+
@polygon.contains_point?(BorderPatrol::Point.new(0.000001, 0.000001)).should be_true
|
105
|
+
end
|
106
|
+
|
107
|
+
it "includes points at the vertices" do
|
108
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-10, 0)).should be_true
|
109
|
+
end
|
110
|
+
|
111
|
+
it "is false if the point is outside of the polygon" do
|
112
|
+
@polygon.contains_point?(BorderPatrol::Point.new(9, 5)).should be_false
|
113
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-5, 8)).should be_false
|
114
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-10, -1)).should be_false
|
115
|
+
@polygon.contains_point?(BorderPatrol::Point.new(-20, -20)).should be_false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#inside_bounding_box?" do
|
120
|
+
before do
|
121
|
+
points = [BorderPatrol::Point.new(-10, 0), BorderPatrol::Point.new(10, 0), BorderPatrol::Point.new(0, 10)]
|
122
|
+
@polygon = BorderPatrol::Polygon.new(points)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "is false if it is outside the bounding box" do
|
126
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(-10, -1)).should be_false
|
127
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(-20, -20)).should be_false
|
128
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(1, 20)).should be_false
|
129
|
+
end
|
130
|
+
|
131
|
+
it "returns true if it is inside the bounding box" do
|
132
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(9, 5)).should be_true
|
133
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(-5, 8)).should be_true
|
134
|
+
@polygon.inside_bounding_box?(BorderPatrol::Point.new(1, 1)).should be_true
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BorderPatrol::Region do
|
4
|
+
it "is a Set" do
|
5
|
+
BorderPatrol::Region.new.should be_a Set
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#contains_point?" do
|
9
|
+
def point
|
10
|
+
|
11
|
+
end
|
12
|
+
def polygon(start=0)
|
13
|
+
BorderPatrol::Polygon.new(
|
14
|
+
BorderPatrol::Point.new(start,start),
|
15
|
+
BorderPatrol::Point.new(start + 10, start),
|
16
|
+
BorderPatrol::Point.new(start + 10, start + 10),
|
17
|
+
BorderPatrol::Point.new(start, start + 10))
|
18
|
+
end
|
19
|
+
subject { BorderPatrol::Region.new(@polygons) }
|
20
|
+
|
21
|
+
it "raises an argument error if contains_point? takes more than 3 arguments" do
|
22
|
+
expect { subject.contains_point? }.to raise_exception ArgumentError
|
23
|
+
expect { subject.contains_point?(1,2,3) }.to raise_exception ArgumentError
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns true if any polygon contains the point" do
|
27
|
+
point = BorderPatrol::Point.new(1,2)
|
28
|
+
@polygons = [polygon, polygon(30)]
|
29
|
+
|
30
|
+
subject.contains_point?(point).should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns false if no polygons contain the point" do
|
34
|
+
point = BorderPatrol::Point.new(-1,-2)
|
35
|
+
@polygons = [polygon, polygon(30)]
|
36
|
+
|
37
|
+
subject.contains_point?(point).should be_false
|
38
|
+
end
|
39
|
+
|
40
|
+
it "transforms (x,y) coordinates passed in into a point" do
|
41
|
+
@polygons = [polygon, polygon(30)]
|
42
|
+
|
43
|
+
subject.contains_point?(1,2).should be_true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BorderPatrol do
|
4
|
+
describe ".parse_kml" do
|
5
|
+
it "returns a BorderPatrol::Region containing a BorderPatrol::Polygon for each polygon in the KML file" do
|
6
|
+
kml_data = File.read("#{File.dirname(__FILE__)}/../support/multi-polygon-test.kml")
|
7
|
+
region = BorderPatrol.parse_kml(kml_data)
|
8
|
+
region.length.should == 3
|
9
|
+
region.each {|p| p.should be_a BorderPatrol::Polygon}
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when there is only one polygon" do
|
13
|
+
it "returns a region containing a single polygon" do
|
14
|
+
kml_data = File.read("#{File.dirname(__FILE__)}/../support/colorado-test.kml")
|
15
|
+
region = BorderPatrol.parse_kml(kml_data)
|
16
|
+
region.length.should == 1
|
17
|
+
region.each {|p| p.should be_a BorderPatrol::Polygon}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ".parse_kml_polygon_data" do
|
23
|
+
it "returns a BorderPatrol::Polygon with the points from the kml data in the correct order" do
|
24
|
+
kml = <<-EOM
|
25
|
+
<Polygon>
|
26
|
+
<outerBoundaryIs>
|
27
|
+
<LinearRing>
|
28
|
+
<tessellate>1</tessellate>
|
29
|
+
<coordinates>
|
30
|
+
-10,25,0.000000
|
31
|
+
-1,30,0.000000
|
32
|
+
10,1,0.000000
|
33
|
+
0, -5,000000
|
34
|
+
-10,25,0.000000
|
35
|
+
</coordinates>
|
36
|
+
</LinearRing>
|
37
|
+
</outerBoundaryIs>
|
38
|
+
</Polygon>
|
39
|
+
EOM
|
40
|
+
polygon = BorderPatrol.parse_kml_polygon_data(kml)
|
41
|
+
polygon.should == BorderPatrol::Polygon.new(BorderPatrol::Point.new(-10, 25), BorderPatrol::Point.new(-1, 30), BorderPatrol::Point.new(10, 1), BorderPatrol::Point.new(0, -5))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe BorderPatrol::Point do
|
46
|
+
describe "==" do
|
47
|
+
it "is true if both points contain the same values" do
|
48
|
+
BorderPatrol::Point.new(1,2).should == BorderPatrol::Point.new(1,2)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "is true if one point contains floats and one contains integers" do
|
52
|
+
BorderPatrol::Point.new(1,2.0).should == BorderPatrol::Point.new(1.0,2)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "is false if the points contain different values" do
|
56
|
+
BorderPatrol::Point.new(1,3).should_not == BorderPatrol::Point.new(1.0,2)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
base_dir = File.expand_path("#{File.dirname(__FILE__)}/..")
|
8
|
+
app_dirs = ['lib', 'lib/kaml_pen']
|
9
|
+
|
10
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
|
11
|
+
|
12
|
+
app_dirs.each do |app_dir|
|
13
|
+
Dir["#{base_dir}/spec/#{app_dir}/*.rb"].each do |f|
|
14
|
+
f.sub!('/spec','')
|
15
|
+
f.sub!('_spec','')
|
16
|
+
require f
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Spec::Runner.configure do |config|
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<kml xmlns="http://earth.google.com/kml/2.2">
|
3
|
+
<Document>
|
4
|
+
<name>Colorado</name>
|
5
|
+
<description><![CDATA[]]></description>
|
6
|
+
<Style id="style1">
|
7
|
+
<LineStyle>
|
8
|
+
<color>40000000</color>
|
9
|
+
<width>3</width>
|
10
|
+
</LineStyle>
|
11
|
+
<PolyStyle>
|
12
|
+
<color>73FF0000</color>
|
13
|
+
<fill>1</fill>
|
14
|
+
<outline>1</outline>
|
15
|
+
</PolyStyle>
|
16
|
+
</Style>
|
17
|
+
<Placemark>
|
18
|
+
<name>Shape 1</name>
|
19
|
+
<description><![CDATA[]]></description>
|
20
|
+
<styleUrl>#style1</styleUrl>
|
21
|
+
<Polygon>
|
22
|
+
<outerBoundaryIs>
|
23
|
+
<LinearRing>
|
24
|
+
<tessellate>1</tessellate>
|
25
|
+
<coordinates>
|
26
|
+
-109.050293,41.008923,0.000000
|
27
|
+
-102.057495,41.004776,0.000000
|
28
|
+
-102.046509,36.993778,0.000000
|
29
|
+
-109.044800,36.998165,0.000000
|
30
|
+
-109.050293,41.008923,0.000000
|
31
|
+
</coordinates>
|
32
|
+
</LinearRing>
|
33
|
+
</outerBoundaryIs>
|
34
|
+
</Polygon>
|
35
|
+
</Placemark>
|
36
|
+
</Document>
|
37
|
+
</kml>
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# Originally by Nicholas A. Evans. See LICENSE.txt at
|
2
|
+
# http://gist.github.com/275257
|
3
|
+
#
|
4
|
+
# Note: includes modifications from the original to use '=' for the progress
|
5
|
+
# bar mark and keep the cursor from hopping all around during the animation.
|
6
|
+
#
|
7
|
+
# This version is compatible with RSpec 1.2.9 and ProgressBar 0.9.0.
|
8
|
+
|
9
|
+
require 'spec/runner/formatter/base_text_formatter'
|
10
|
+
require 'progressbar'
|
11
|
+
|
12
|
+
module Spec
|
13
|
+
module Runner
|
14
|
+
module Formatter
|
15
|
+
class CompactProgressBarFormatter < BaseTextFormatter
|
16
|
+
# Threshold for slow specs, in seconds.
|
17
|
+
# Anything that takes longer than this will be printed out
|
18
|
+
THRESHOLD = 1.0 unless defined?(THRESHOLD)
|
19
|
+
|
20
|
+
attr_reader :total, :current
|
21
|
+
|
22
|
+
def start(example_count)
|
23
|
+
@current = 0
|
24
|
+
@total = example_count
|
25
|
+
@error_state = :all_passing
|
26
|
+
@pbar = ProgressBar.new("#{example_count} examples", example_count, output)
|
27
|
+
@pbar.instance_variable_set("@bar_mark", "=")
|
28
|
+
end
|
29
|
+
|
30
|
+
def example_started(example)
|
31
|
+
super
|
32
|
+
@start_time = Time.now
|
33
|
+
end
|
34
|
+
|
35
|
+
def example_passed(example)
|
36
|
+
print_warning_if_slow(example_group.description,
|
37
|
+
example.description,
|
38
|
+
Time.now - @start_time)
|
39
|
+
increment
|
40
|
+
end
|
41
|
+
|
42
|
+
def example_pending(example, message, deprecated_pending_location=nil)
|
43
|
+
immediately_dump_pending(example.description, message, pending_caller)
|
44
|
+
mark_error_state_pending
|
45
|
+
increment
|
46
|
+
end
|
47
|
+
|
48
|
+
def example_failed(example, counter, failure)
|
49
|
+
immediately_dump_failure(counter, failure)
|
50
|
+
mark_error_state_failed
|
51
|
+
increment
|
52
|
+
end
|
53
|
+
|
54
|
+
def start_dump
|
55
|
+
with_color do
|
56
|
+
@pbar.finish
|
57
|
+
end
|
58
|
+
output.flush
|
59
|
+
end
|
60
|
+
|
61
|
+
def dump_failure(*args)
|
62
|
+
# no-op; we summarized failures as we were running
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(sym, *args)
|
66
|
+
# ignore
|
67
|
+
end
|
68
|
+
|
69
|
+
# Adapted from BaseTextFormatter#dump_failure
|
70
|
+
def immediately_dump_failure(counter, failure)
|
71
|
+
erase_current_line
|
72
|
+
output.print "#{counter.to_s}) "
|
73
|
+
output.puts colorize_failure("#{failure.header}\n#{failure.exception.message}", failure)
|
74
|
+
output.puts format_backtrace(failure.exception.backtrace)
|
75
|
+
output.puts
|
76
|
+
end
|
77
|
+
|
78
|
+
# Adapted from BaseTextFormatter#dump_pending
|
79
|
+
def immediately_dump_pending(desc, msg, called_from)
|
80
|
+
erase_current_line
|
81
|
+
output.puts yellow("PENDING SPEC:") + " #{desc} (#{msg})"
|
82
|
+
output.puts " Called from #{called_from}" if called_from
|
83
|
+
end
|
84
|
+
|
85
|
+
def increment
|
86
|
+
with_color do
|
87
|
+
@current += 1
|
88
|
+
# Since we're constantly erasing the line, make sure the progress is
|
89
|
+
# printed even when the bar hasn't changed
|
90
|
+
@pbar.instance_variable_set("@previous", 0)
|
91
|
+
@pbar.instance_variable_set("@title", " #{current}/#{total}")
|
92
|
+
@pbar.inc
|
93
|
+
end
|
94
|
+
output.flush
|
95
|
+
end
|
96
|
+
|
97
|
+
ERROR_STATE_COLORS = {
|
98
|
+
:all_passing => "\e[32m", # green
|
99
|
+
:some_pending => "\e[33m", # yellow
|
100
|
+
:some_failed => "\e[31m", # red
|
101
|
+
} unless defined?(ERROR_STATE_COLORS)
|
102
|
+
|
103
|
+
def with_color
|
104
|
+
output.print "\e[?25l" + ERROR_STATE_COLORS[@error_state] if colour?
|
105
|
+
yield
|
106
|
+
output.print "\e[0m\e[?25h" if colour?
|
107
|
+
end
|
108
|
+
|
109
|
+
def mark_error_state_failed
|
110
|
+
@error_state = :some_failed
|
111
|
+
end
|
112
|
+
|
113
|
+
def mark_error_state_pending
|
114
|
+
@error_state = :some_pending unless @error_state == :some_failed
|
115
|
+
end
|
116
|
+
|
117
|
+
def erase_current_line
|
118
|
+
output.print "\e[K"
|
119
|
+
end
|
120
|
+
|
121
|
+
def print_warning_if_slow(group, example, elapsed)
|
122
|
+
if elapsed > THRESHOLD
|
123
|
+
erase_current_line
|
124
|
+
output.print yellow("SLOW SPEC: #{sprintf("%.4f", elapsed)} ")
|
125
|
+
output.print " #{group} #{example}"
|
126
|
+
output.puts
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<kml xmlns="http://earth.google.com/kml/2.2">
|
3
|
+
<Document>
|
4
|
+
<name>Multi-Polygon</name>
|
5
|
+
<description><![CDATA[]]></description>
|
6
|
+
<Style id="style1">
|
7
|
+
<LineStyle>
|
8
|
+
<color>40000000</color>
|
9
|
+
<width>3</width>
|
10
|
+
</LineStyle>
|
11
|
+
<PolyStyle>
|
12
|
+
<color>73FF0000</color>
|
13
|
+
<fill>1</fill>
|
14
|
+
<outline>1</outline>
|
15
|
+
</PolyStyle>
|
16
|
+
</Style>
|
17
|
+
<Style id="style3">
|
18
|
+
<LineStyle>
|
19
|
+
<color>40000000</color>
|
20
|
+
<width>3</width>
|
21
|
+
</LineStyle>
|
22
|
+
<PolyStyle>
|
23
|
+
<color>73FF0000</color>
|
24
|
+
<fill>1</fill>
|
25
|
+
<outline>1</outline>
|
26
|
+
</PolyStyle>
|
27
|
+
</Style>
|
28
|
+
<Style id="style2">
|
29
|
+
<LineStyle>
|
30
|
+
<color>40000000</color>
|
31
|
+
<width>3</width>
|
32
|
+
</LineStyle>
|
33
|
+
<PolyStyle>
|
34
|
+
<color>73FF0000</color>
|
35
|
+
<fill>1</fill>
|
36
|
+
<outline>1</outline>
|
37
|
+
</PolyStyle>
|
38
|
+
</Style>
|
39
|
+
<Placemark>
|
40
|
+
<name>Colorado</name>
|
41
|
+
<description><![CDATA[]]></description>
|
42
|
+
<styleUrl>#style1</styleUrl>
|
43
|
+
<Polygon>
|
44
|
+
<outerBoundaryIs>
|
45
|
+
<LinearRing>
|
46
|
+
<tessellate>1</tessellate>
|
47
|
+
<coordinates>
|
48
|
+
-109.053040,41.002705,0.000000
|
49
|
+
-102.046509,41.006847,0.000000
|
50
|
+
-102.041016,36.991585,0.000000
|
51
|
+
-109.048920,36.997070,0.000000
|
52
|
+
-109.053040,41.002705,0.000000
|
53
|
+
</coordinates>
|
54
|
+
</LinearRing>
|
55
|
+
</outerBoundaryIs>
|
56
|
+
</Polygon>
|
57
|
+
</Placemark>
|
58
|
+
<Placemark>
|
59
|
+
<name>Paris</name>
|
60
|
+
<description><![CDATA[]]></description>
|
61
|
+
<styleUrl>#style3</styleUrl>
|
62
|
+
<Polygon>
|
63
|
+
<outerBoundaryIs>
|
64
|
+
<LinearRing>
|
65
|
+
<tessellate>1</tessellate>
|
66
|
+
<coordinates>
|
67
|
+
1.593018,49.109837,0.000000
|
68
|
+
1.571045,48.257599,0.000000
|
69
|
+
3.186035,48.275883,0.000000
|
70
|
+
3.186035,49.167339,0.000000
|
71
|
+
1.593018,49.109837,0.000000
|
72
|
+
</coordinates>
|
73
|
+
</LinearRing>
|
74
|
+
</outerBoundaryIs>
|
75
|
+
</Polygon>
|
76
|
+
</Placemark>
|
77
|
+
<Placemark>
|
78
|
+
<name>Australia</name>
|
79
|
+
<description><![CDATA[]]></description>
|
80
|
+
<styleUrl>#style2</styleUrl>
|
81
|
+
<Polygon>
|
82
|
+
<outerBoundaryIs>
|
83
|
+
<LinearRing>
|
84
|
+
<tessellate>1</tessellate>
|
85
|
+
<coordinates>
|
86
|
+
120.673828,-14.179186,0.000000
|
87
|
+
107.534180,-25.918526,0.000000
|
88
|
+
111.840820,-37.544579,0.000000
|
89
|
+
148.183594,-44.559162,0.000000
|
90
|
+
157.543945,-30.789038,0.000000
|
91
|
+
148.051758,-10.617418,0.000000
|
92
|
+
135.131836,-9.188870,0.000000
|
93
|
+
120.673828,-14.179186,0.000000
|
94
|
+
</coordinates>
|
95
|
+
</LinearRing>
|
96
|
+
</outerBoundaryIs>
|
97
|
+
</Polygon>
|
98
|
+
</Placemark>
|
99
|
+
</Document>
|
100
|
+
</kml>
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: border_patrol
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Zach Brock
|
14
|
+
- Matt Wilson
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-10-20 00:00:00 -07:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - "="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 49
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 8
|
32
|
+
- 7
|
33
|
+
version: 0.8.7
|
34
|
+
name: rake
|
35
|
+
prerelease: false
|
36
|
+
requirement: *id001
|
37
|
+
type: :development
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - "="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 27
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
version: 1.3.0
|
50
|
+
name: rspec
|
51
|
+
prerelease: false
|
52
|
+
requirement: *id002
|
53
|
+
type: :development
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
name: progressbar
|
65
|
+
prerelease: false
|
66
|
+
requirement: *id003
|
67
|
+
type: :development
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - "="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 113
|
75
|
+
segments:
|
76
|
+
- 1
|
77
|
+
- 4
|
78
|
+
- 3
|
79
|
+
- 1
|
80
|
+
version: 1.4.3.1
|
81
|
+
name: nokogiri
|
82
|
+
prerelease: false
|
83
|
+
requirement: *id004
|
84
|
+
type: :runtime
|
85
|
+
description: Lets you import a KML file and then check if points are inside or outside the polygons the file defines.
|
86
|
+
email: eng@squareup.com
|
87
|
+
executables: []
|
88
|
+
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files: []
|
92
|
+
|
93
|
+
files:
|
94
|
+
- lib/border_patrol/polygon.rb
|
95
|
+
- lib/border_patrol/region.rb
|
96
|
+
- lib/border_patrol.rb
|
97
|
+
- lib/VERSION
|
98
|
+
- spec/lib/border_patrol/polygon_spec.rb
|
99
|
+
- spec/lib/border_patrol/region_spec.rb
|
100
|
+
- spec/lib/border_patrol_spec.rb
|
101
|
+
- spec/spec.opts
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/support/colorado-test.kml
|
104
|
+
- spec/support/formatters/compact_progress_bar_formatter.rb
|
105
|
+
- spec/support/multi-polygon-test.kml
|
106
|
+
- border_patrol.gemspec
|
107
|
+
- Rakefile
|
108
|
+
- README.markdown
|
109
|
+
- .gitignore
|
110
|
+
- Gemfile
|
111
|
+
- Gemfile.lock
|
112
|
+
has_rdoc: true
|
113
|
+
homepage: http://github.com/square/border_patrol
|
114
|
+
licenses: []
|
115
|
+
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options:
|
118
|
+
- --charset=UTF-8
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
hash: 3
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
version: "0"
|
139
|
+
requirements: []
|
140
|
+
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 1.3.7
|
143
|
+
signing_key:
|
144
|
+
specification_version: 3
|
145
|
+
summary: Import and query KML regions
|
146
|
+
test_files:
|
147
|
+
- spec/lib/border_patrol/polygon_spec.rb
|
148
|
+
- spec/lib/border_patrol/region_spec.rb
|
149
|
+
- spec/lib/border_patrol_spec.rb
|
150
|
+
- spec/spec.opts
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/support/colorado-test.kml
|
153
|
+
- spec/support/formatters/compact_progress_bar_formatter.rb
|
154
|
+
- spec/support/multi-polygon-test.kml
|