local-geocoder 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/bin/local_geocode +42 -0
- data/ext/geometry/GeometryService.java +49 -0
- data/ext/geometry/Point.java +27 -0
- data/ext/geometry/Polygon.java +88 -0
- data/ext/geometry/RPoint.java +72 -0
- data/ext/geometry/RPolygon.java +111 -0
- data/ext/geometry/RRect.java +96 -0
- data/ext/geometry/Rect.java +49 -0
- data/ext/geometry/extconf.rb +5 -0
- data/lib/local_geocoder.rb +23 -0
- data/lib/local_geocoder/data_source.rb +57 -0
- data/lib/local_geocoder/geocoder.rb +48 -0
- data/lib/local_geocoder/geometry/point.rb +18 -0
- data/lib/local_geocoder/geometry/polygon.rb +69 -0
- data/lib/local_geocoder/geometry/rect.rb +22 -0
- data/lib/local_geocoder/version.rb +3 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MGIwNmFlYmZlNGVmNDExZDZkOTMxZGJmODU4NGNhNDIyMzY3MTZkNA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDIxMWZmNmVmZGYwZTZmYmFkYWVhNDg3YjhhYTY4ZTNhOGQ5YmJjMQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ODc3YjAzMGNiNjQyZTU3NDgwOTAzOTI2ZWZjOGYzOTk1ODNjNWMyZDQ3MWIz
|
10
|
+
ZThkMjQwMDRmMTRkN2ZlOTdhYTQ0Njg0YzY3NWQzMTUxM2I4YWQ1MmQ3ZDc1
|
11
|
+
NTQ3ODE1N2MyMTFjYjIyYzMzOTEwZjkyZGM1N2JjYzM2NGFmZjY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZGRkNGQ3NjQ2OTE4MTUyYzU5Yjg3MzVhODg5Yjg4ZWI2MjNmY2UxZmE0YzQw
|
14
|
+
ZDAyZmNiZWNmYTJiNmY1ZjU5ODQ5MjRjMTlmODQ2OTgwMjYyMWY3YmE3ZTVj
|
15
|
+
YTg0NGExZjA1MDFiYzQ0ZGUyMjQ5MjIwN2FlODNlNWViZjA0OTU=
|
data/bin/local_geocode
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "../lib"))
|
4
|
+
require "local_geocoder"
|
5
|
+
require "trollop"
|
6
|
+
require "csv"
|
7
|
+
|
8
|
+
DEFAULT_TEMPLATE = '(?<lng>\-?\d+.?\d+)[,\t ]\s?(?<lat>\-?\d+.?\d+)'
|
9
|
+
|
10
|
+
opts = Trollop::options do
|
11
|
+
version "local_geocode #{LocalGeocoder::VERSION}"
|
12
|
+
banner <<-EOS
|
13
|
+
Local Geocoder
|
14
|
+
---------------
|
15
|
+
Reverse geocodes lng,lat pairs into Country codes (and State + County within the USA). Each line of Stdin is read and has lng,lat pairs extracted (see --template below for how) and the resulting reverse geocode is output to Stdout.
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
local_geocode [options] file
|
19
|
+
|
20
|
+
Example:
|
21
|
+
> echo "-122.4194155, 37.7749295" | local_geocode
|
22
|
+
> United States of America (USA), California (CA), San Francisco
|
23
|
+
|
24
|
+
Options:
|
25
|
+
EOS
|
26
|
+
opt 'template', "The regex used to parse lng,lat paris from the input stream", :default => DEFAULT_TEMPLATE, :short => :t
|
27
|
+
end
|
28
|
+
|
29
|
+
require "local_geocoder"
|
30
|
+
|
31
|
+
lc = LocalGeocoder::Geocoder.new
|
32
|
+
ARGF.each_line do |l|
|
33
|
+
data = l.match(Regexp.new(opts["template"]))
|
34
|
+
result = if data.nil?
|
35
|
+
LocalGeocoder::Result.new
|
36
|
+
else
|
37
|
+
lng, lat = data[:lng].to_f, data[:lat].to_f
|
38
|
+
lc.reverse_geocode(lng, lat)
|
39
|
+
end
|
40
|
+
puts result.inspect
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
package geometry;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyArray;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyFixnum;
|
7
|
+
import org.jruby.RubyModule;
|
8
|
+
import org.jruby.RubyObject;
|
9
|
+
import org.jruby.anno.JRubyClass;
|
10
|
+
import org.jruby.anno.JRubyMethod;
|
11
|
+
import org.jruby.runtime.ObjectAllocator;
|
12
|
+
import org.jruby.runtime.ThreadContext;
|
13
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
14
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
15
|
+
|
16
|
+
public class GeometryService implements BasicLibraryService {
|
17
|
+
private Ruby runtime;
|
18
|
+
|
19
|
+
public boolean basicLoad(Ruby runtime) {
|
20
|
+
this.runtime = runtime;
|
21
|
+
RubyModule lc = runtime.defineModule("LocalGeocoder");
|
22
|
+
RubyModule geo = lc.defineModuleUnder("Geometry");
|
23
|
+
|
24
|
+
RubyClass point = geo.defineClassUnder("Point", runtime.getObject(), new ObjectAllocator() {
|
25
|
+
public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
|
26
|
+
return new RPoint(runtime, rubyClass);
|
27
|
+
}
|
28
|
+
});
|
29
|
+
|
30
|
+
RubyClass rect = geo.defineClassUnder("Rect", runtime.getObject(), new ObjectAllocator() {
|
31
|
+
public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
|
32
|
+
return new RRect(runtime, rubyClass);
|
33
|
+
}
|
34
|
+
});
|
35
|
+
|
36
|
+
RubyClass polygon = geo.defineClassUnder("Polygon", runtime.getObject(), new ObjectAllocator() {
|
37
|
+
public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
|
38
|
+
return new RPolygon(runtime, rubyClass);
|
39
|
+
}
|
40
|
+
});
|
41
|
+
|
42
|
+
point.defineAnnotatedMethods(RPoint.class);
|
43
|
+
rect.defineAnnotatedMethods(RRect.class);
|
44
|
+
polygon.defineAnnotatedMethods(RPolygon.class);
|
45
|
+
return true;
|
46
|
+
}
|
47
|
+
|
48
|
+
}
|
49
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
package geometry;
|
2
|
+
|
3
|
+
public class Point {
|
4
|
+
|
5
|
+
double x;
|
6
|
+
double y;
|
7
|
+
|
8
|
+
public Point(double x, double y) {
|
9
|
+
this.x = x;
|
10
|
+
this.y = y;
|
11
|
+
}
|
12
|
+
|
13
|
+
public double getX() {
|
14
|
+
return this.x;
|
15
|
+
}
|
16
|
+
|
17
|
+
public double getY() {
|
18
|
+
return this.y;
|
19
|
+
}
|
20
|
+
|
21
|
+
public String toString() {
|
22
|
+
return String.format("[%s,%s]", x, y);
|
23
|
+
}
|
24
|
+
|
25
|
+
}
|
26
|
+
|
27
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
package geometry;
|
2
|
+
|
3
|
+
public class Polygon {
|
4
|
+
|
5
|
+
Point[] points;
|
6
|
+
Rect boundingBox;
|
7
|
+
|
8
|
+
public Polygon(Point[] points) {
|
9
|
+
this.points = points;
|
10
|
+
}
|
11
|
+
|
12
|
+
public Point[] getPoints() {
|
13
|
+
return this.points;
|
14
|
+
}
|
15
|
+
|
16
|
+
public int numberOfPoints() {
|
17
|
+
return this.points.length;
|
18
|
+
}
|
19
|
+
|
20
|
+
public Rect boundingBox() {
|
21
|
+
if (boundingBox == null) {
|
22
|
+
double minX, minY, maxX, maxY;
|
23
|
+
minX = minY = Double.POSITIVE_INFINITY;
|
24
|
+
maxX = maxY = Double.NEGATIVE_INFINITY;
|
25
|
+
|
26
|
+
for (Point p: this.points) {
|
27
|
+
|
28
|
+
double x = p.getX();
|
29
|
+
double y = p.getY();
|
30
|
+
|
31
|
+
if (x < minX)
|
32
|
+
minX = x;
|
33
|
+
if (x > maxX)
|
34
|
+
maxX = x;
|
35
|
+
|
36
|
+
if (y < minY)
|
37
|
+
minY = y;
|
38
|
+
if (y > maxY)
|
39
|
+
maxY = y;
|
40
|
+
}
|
41
|
+
|
42
|
+
this.boundingBox = new Rect(minX, minY, maxX-minX, maxY-minY);
|
43
|
+
}
|
44
|
+
|
45
|
+
return this.boundingBox;
|
46
|
+
}
|
47
|
+
|
48
|
+
public String toString() {
|
49
|
+
StringBuilder sb = new StringBuilder();
|
50
|
+
|
51
|
+
for (Point p : points) {
|
52
|
+
sb.append( p.toString() + "," );
|
53
|
+
}
|
54
|
+
return sb.toString();
|
55
|
+
}
|
56
|
+
|
57
|
+
public boolean containsPoint(Point point) {
|
58
|
+
if (!this.boundingBox().containsPoint(point))
|
59
|
+
return false;
|
60
|
+
|
61
|
+
boolean containsPoint = false;
|
62
|
+
int i = -1;
|
63
|
+
int j = this.points.length - 1;
|
64
|
+
while (++i < this.points.length) {
|
65
|
+
Point p1 = this.points[i];
|
66
|
+
Point p2 = this.points[j];
|
67
|
+
if (this.withinYBounds(point, p1, p2)) {
|
68
|
+
if (this.intersectsLineSegment(point, p1, p2)) {
|
69
|
+
containsPoint = !containsPoint;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
j = i;
|
73
|
+
}
|
74
|
+
return containsPoint;
|
75
|
+
}
|
76
|
+
|
77
|
+
private boolean intersectsLineSegment(Point point, Point p1, Point p2) {
|
78
|
+
return (point.getX() < (p2.getX() - p1.getX()) * (point.getY() - p1.getY()) / (p2.getY() - p1.getY()) + p1.getX());
|
79
|
+
}
|
80
|
+
|
81
|
+
private boolean withinYBounds(Point point, Point p1, Point p2) {
|
82
|
+
return (p1.getY() <= point.getY() && point.getY() < p2.getY()) ||
|
83
|
+
(p2.getY() <= point.getY() && point.getY() < p1.getY());
|
84
|
+
}
|
85
|
+
|
86
|
+
}
|
87
|
+
|
88
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
package geometry;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyArray;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyFixnum;
|
7
|
+
import org.jruby.RubyModule;
|
8
|
+
import org.jruby.RubyObject;
|
9
|
+
import org.jruby.anno.JRubyClass;
|
10
|
+
import org.jruby.anno.JRubyMethod;
|
11
|
+
import org.jruby.runtime.ObjectAllocator;
|
12
|
+
import org.jruby.runtime.ThreadContext;
|
13
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
14
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
15
|
+
|
16
|
+
@JRubyClass(name = "LocalGeocoder::Geometry::Point")
|
17
|
+
public class RPoint extends RubyObject {
|
18
|
+
|
19
|
+
Point point;
|
20
|
+
IRubyObject data;
|
21
|
+
|
22
|
+
public RPoint(final Ruby runtime, RubyClass rubyClass) {
|
23
|
+
super(runtime, rubyClass);
|
24
|
+
}
|
25
|
+
|
26
|
+
@JRubyMethod(name = "initialize", required = 2, optional = 1)
|
27
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
|
28
|
+
Ruby ruby = context.getRuntime();
|
29
|
+
double x = args[0].convertToFloat().getDoubleValue();
|
30
|
+
double y = args[1].convertToFloat().getDoubleValue();
|
31
|
+
|
32
|
+
this.point = new Point(x,y);
|
33
|
+
this.data = context.nil;
|
34
|
+
if (args.length > 2) {
|
35
|
+
this.data = args[2];
|
36
|
+
}
|
37
|
+
|
38
|
+
return context.nil;
|
39
|
+
}
|
40
|
+
|
41
|
+
public void setPoint(Point point) {
|
42
|
+
this.point = point;
|
43
|
+
}
|
44
|
+
|
45
|
+
public Point getJava() {
|
46
|
+
return this.point;
|
47
|
+
}
|
48
|
+
|
49
|
+
@JRubyMethod(name = "x")
|
50
|
+
public IRubyObject x(ThreadContext context) {
|
51
|
+
Ruby ruby = context.getRuntime();
|
52
|
+
return ruby.newFloat(point.getX());
|
53
|
+
}
|
54
|
+
|
55
|
+
@JRubyMethod(name = "y")
|
56
|
+
public IRubyObject y(ThreadContext context) {
|
57
|
+
Ruby ruby = context.getRuntime();
|
58
|
+
return ruby.newFloat(point.getY());
|
59
|
+
}
|
60
|
+
|
61
|
+
@JRubyMethod(name = "data")
|
62
|
+
public IRubyObject data(ThreadContext context) {
|
63
|
+
return data;
|
64
|
+
}
|
65
|
+
|
66
|
+
@JRubyMethod(name = "inspect")
|
67
|
+
public IRubyObject inspect(ThreadContext context) {
|
68
|
+
Ruby ruby = context.getRuntime();
|
69
|
+
return ruby.newString(point.toString());
|
70
|
+
}
|
71
|
+
|
72
|
+
}
|
@@ -0,0 +1,111 @@
|
|
1
|
+
package geometry;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyArray;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyFixnum;
|
7
|
+
import org.jruby.RubyModule;
|
8
|
+
import org.jruby.RubyObject;
|
9
|
+
import org.jruby.anno.JRubyClass;
|
10
|
+
import org.jruby.anno.JRubyMethod;
|
11
|
+
import org.jruby.runtime.ObjectAllocator;
|
12
|
+
import org.jruby.runtime.ThreadContext;
|
13
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
14
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
15
|
+
|
16
|
+
@JRubyClass(name = "LocalGeocoder::Geometry::Polygon")
|
17
|
+
public class RPolygon extends RubyObject {
|
18
|
+
|
19
|
+
Polygon polygon;
|
20
|
+
|
21
|
+
public RPolygon(final Ruby runtime, RubyClass rubyClass) {
|
22
|
+
super(runtime, rubyClass);
|
23
|
+
}
|
24
|
+
|
25
|
+
public void setPolygon(Polygon polygon) {
|
26
|
+
this.polygon = polygon;
|
27
|
+
}
|
28
|
+
|
29
|
+
@JRubyMethod(name = "initialize")
|
30
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject rps) {
|
31
|
+
Ruby ruby = context.getRuntime();
|
32
|
+
|
33
|
+
RubyArray rPoints = (RubyArray) rps;
|
34
|
+
|
35
|
+
Point[] points = new Point[rPoints.getLength()];
|
36
|
+
for (int i = 0; i < rPoints.getLength(); i++) {
|
37
|
+
points[i] = ((RPoint) rPoints.get(i)).getJava();
|
38
|
+
}
|
39
|
+
|
40
|
+
this.polygon = new Polygon(points);
|
41
|
+
|
42
|
+
return context.nil;
|
43
|
+
}
|
44
|
+
|
45
|
+
@JRubyMethod(meta = true, name = "from_point_array")
|
46
|
+
public static IRubyObject fromPointArray(ThreadContext context, IRubyObject klazz, IRubyObject pts) {
|
47
|
+
Ruby ruby = context.getRuntime();
|
48
|
+
|
49
|
+
RubyArray rpts = (RubyArray) pts;
|
50
|
+
|
51
|
+
Point[] points = new Point[rpts.getLength()];
|
52
|
+
for (int i = 0; i < rpts.getLength(); i++) {
|
53
|
+
double x = ((Number) ((RubyArray) rpts.get(i)).get(0)).doubleValue();
|
54
|
+
double y = ((Number) ((RubyArray) rpts.get(i)).get(1)).doubleValue();
|
55
|
+
points[i] = new Point(x,y);
|
56
|
+
}
|
57
|
+
|
58
|
+
IRubyObject obj = ((RubyClass) klazz).allocate();
|
59
|
+
((RPolygon) obj).setPolygon(new Polygon(points));
|
60
|
+
|
61
|
+
return obj;
|
62
|
+
}
|
63
|
+
|
64
|
+
@JRubyMethod
|
65
|
+
public IRubyObject number_of_points(ThreadContext context) {
|
66
|
+
Ruby ruby = context.getRuntime();
|
67
|
+
int i = this.polygon.numberOfPoints();
|
68
|
+
return ruby.newFixnum(i);
|
69
|
+
}
|
70
|
+
|
71
|
+
@JRubyMethod(name = "points")
|
72
|
+
public IRubyObject points(ThreadContext context) {
|
73
|
+
Ruby ruby = context.getRuntime();
|
74
|
+
|
75
|
+
Point[] points = this.polygon.getPoints();
|
76
|
+
RPoint[] rPoints = new RPoint[points.length];
|
77
|
+
for (int i = 0; i < points.length; i++) {
|
78
|
+
RPoint rPoint = new RPoint(ruby, (RubyClass)ruby.getClassFromPath("LocalGeocoder::Geometry::Point"));
|
79
|
+
rPoint.setPoint(points[i]);
|
80
|
+
rPoints[i] = rPoint;
|
81
|
+
}
|
82
|
+
|
83
|
+
return ruby.newArray(rPoints);
|
84
|
+
}
|
85
|
+
|
86
|
+
@JRubyMethod(name = "bounding_box")
|
87
|
+
public IRubyObject boundingBox(ThreadContext context) {
|
88
|
+
Ruby ruby = context.getRuntime();
|
89
|
+
|
90
|
+
Rect rect = this.polygon.boundingBox();
|
91
|
+
RRect rRect = new RRect(ruby, (RubyClass)ruby.getClassFromPath("LocalGeocoder::Geometry::Rect"));
|
92
|
+
rRect.setRect(rect);
|
93
|
+
|
94
|
+
return rRect;
|
95
|
+
}
|
96
|
+
|
97
|
+
@JRubyMethod(name = "contains_point?")
|
98
|
+
public IRubyObject containsPoint(ThreadContext context, IRubyObject rPoint) {
|
99
|
+
Ruby ruby = context.getRuntime();
|
100
|
+
|
101
|
+
boolean result = this.polygon.containsPoint(((RPoint)rPoint).getJava());
|
102
|
+
return ruby.newBoolean(result);
|
103
|
+
}
|
104
|
+
|
105
|
+
@JRubyMethod(name = "inspect")
|
106
|
+
public IRubyObject inspect(ThreadContext context) {
|
107
|
+
Ruby ruby = context.getRuntime();
|
108
|
+
return ruby.newString(polygon.toString());
|
109
|
+
}
|
110
|
+
|
111
|
+
}
|
@@ -0,0 +1,96 @@
|
|
1
|
+
package geometry;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyArray;
|
5
|
+
import org.jruby.RubyClass;
|
6
|
+
import org.jruby.RubyFixnum;
|
7
|
+
import org.jruby.RubyModule;
|
8
|
+
import org.jruby.RubyObject;
|
9
|
+
import org.jruby.anno.JRubyClass;
|
10
|
+
import org.jruby.anno.JRubyMethod;
|
11
|
+
import org.jruby.runtime.ObjectAllocator;
|
12
|
+
import org.jruby.runtime.ThreadContext;
|
13
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
14
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
15
|
+
|
16
|
+
@JRubyClass(name = "LocalGeocoder::Geometry::Rect")
|
17
|
+
public class RRect extends RubyObject {
|
18
|
+
|
19
|
+
Rect rect;
|
20
|
+
|
21
|
+
public RRect(final Ruby runtime, RubyClass rubyClass) {
|
22
|
+
super(runtime, rubyClass);
|
23
|
+
}
|
24
|
+
|
25
|
+
@JRubyMethod(name = "initialize", required = 4)
|
26
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
|
27
|
+
Ruby ruby = context.getRuntime();
|
28
|
+
|
29
|
+
double dX = args[0].convertToFloat().getDoubleValue();
|
30
|
+
double dY = args[1].convertToFloat().getDoubleValue();
|
31
|
+
double dWidth = args[2].convertToFloat().getDoubleValue();
|
32
|
+
double dHeight = args[3].convertToFloat().getDoubleValue();
|
33
|
+
|
34
|
+
this.rect = new Rect(dX,dY,dWidth,dHeight);
|
35
|
+
|
36
|
+
return context.nil;
|
37
|
+
}
|
38
|
+
|
39
|
+
public void setRect(Rect rect) {
|
40
|
+
this.rect = rect;
|
41
|
+
}
|
42
|
+
|
43
|
+
public Rect getJava() {
|
44
|
+
return this.rect;
|
45
|
+
}
|
46
|
+
|
47
|
+
@JRubyMethod(name = "x")
|
48
|
+
public IRubyObject x(ThreadContext context) {
|
49
|
+
Ruby ruby = context.getRuntime();
|
50
|
+
return ruby.newFloat(rect.getX());
|
51
|
+
}
|
52
|
+
|
53
|
+
@JRubyMethod(name = "y")
|
54
|
+
public IRubyObject y(ThreadContext context) {
|
55
|
+
Ruby ruby = context.getRuntime();
|
56
|
+
return ruby.newFloat(rect.getY());
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
@JRubyMethod(name = "width")
|
61
|
+
public IRubyObject widht(ThreadContext context) {
|
62
|
+
Ruby ruby = context.getRuntime();
|
63
|
+
return ruby.newFloat(rect.getWidth());
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
@JRubyMethod(name = "height")
|
68
|
+
public IRubyObject height(ThreadContext context) {
|
69
|
+
Ruby ruby = context.getRuntime();
|
70
|
+
return ruby.newFloat(rect.getHeight());
|
71
|
+
}
|
72
|
+
|
73
|
+
@JRubyMethod(name = "==")
|
74
|
+
public IRubyObject r_eql(ThreadContext context, IRubyObject rRect) {
|
75
|
+
Ruby ruby = context.getRuntime();
|
76
|
+
|
77
|
+
|
78
|
+
return ruby.newBoolean(rect.equals(((RRect) rRect).getJava()));
|
79
|
+
}
|
80
|
+
|
81
|
+
@JRubyMethod(name = "contains_point?")
|
82
|
+
public IRubyObject containsPoint(ThreadContext context, IRubyObject point) {
|
83
|
+
Ruby ruby = context.getRuntime();
|
84
|
+
boolean result = this.rect.containsPoint(((RPoint)point).getJava());
|
85
|
+
|
86
|
+
return ruby.newBoolean(result);
|
87
|
+
}
|
88
|
+
|
89
|
+
@JRubyMethod(name = "inspect")
|
90
|
+
public IRubyObject inspect(ThreadContext context) {
|
91
|
+
Ruby ruby = context.getRuntime();
|
92
|
+
String str = String.format("[%s,%s],[%s,%s]", rect.getX(),rect.getY(),rect.getX()+rect.getWidth(),rect.getY()+rect.getHeight());
|
93
|
+
return ruby.newString(str);
|
94
|
+
}
|
95
|
+
|
96
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
package geometry;
|
2
|
+
|
3
|
+
public class Rect {
|
4
|
+
|
5
|
+
double x;
|
6
|
+
double y;
|
7
|
+
double width;
|
8
|
+
double height;
|
9
|
+
|
10
|
+
public Rect(double x, double y, double width, double height) {
|
11
|
+
this.x = x;
|
12
|
+
this.y = y;
|
13
|
+
this.width = width;
|
14
|
+
this.height = height;
|
15
|
+
}
|
16
|
+
|
17
|
+
public boolean containsPoint(Point point) {
|
18
|
+
return point.getX() >= this.x && point.getY() >= this.y &&
|
19
|
+
point.getX() <= (this.x + this.width) && point.getY() <= (this.y + this.height);
|
20
|
+
}
|
21
|
+
|
22
|
+
public double getX() {
|
23
|
+
return this.x;
|
24
|
+
}
|
25
|
+
|
26
|
+
public double getY() {
|
27
|
+
return this.y;
|
28
|
+
}
|
29
|
+
|
30
|
+
public double getWidth() {
|
31
|
+
return this.width;
|
32
|
+
}
|
33
|
+
|
34
|
+
public double getHeight() {
|
35
|
+
return this.height;
|
36
|
+
}
|
37
|
+
|
38
|
+
@Override public boolean equals(Object other) {
|
39
|
+
boolean result = false;
|
40
|
+
if (other instanceof Rect) {
|
41
|
+
Rect rect = (Rect) other;
|
42
|
+
result = (this.x == rect.getX() && this.y == rect.getY() &&
|
43
|
+
this.width == rect.getWidth() && this.height == rect.getHeight());
|
44
|
+
}
|
45
|
+
return result;
|
46
|
+
}
|
47
|
+
|
48
|
+
}
|
49
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module LocalGeocoder
|
4
|
+
DATA_DIR = "data"
|
5
|
+
end
|
6
|
+
|
7
|
+
require "local_geocoder/version"
|
8
|
+
require "local_geocoder/data_source"
|
9
|
+
require "local_geocoder/geocoder"
|
10
|
+
|
11
|
+
if RUBY_PLATFORM =~ /java/
|
12
|
+
require 'jruby'
|
13
|
+
require 'geometry.jar'
|
14
|
+
|
15
|
+
java_import 'geometry.GeometryService'
|
16
|
+
|
17
|
+
Java::Geometry::GeometryService.new.basicLoad(JRuby.runtime)
|
18
|
+
else
|
19
|
+
require "local_geocoder/geometry/point"
|
20
|
+
require "local_geocoder/geometry/rect"
|
21
|
+
require "local_geocoder/geometry/polygon"
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module LocalGeocoder
|
2
|
+
|
3
|
+
Entity = Struct.new(:short_name, :long_name, :geometries) do
|
4
|
+
def inspect
|
5
|
+
"#{self.long_name}#{self.short_name.empty? ? "" : ' ('+self.short_name+')' }"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class DataSource
|
10
|
+
|
11
|
+
def initialize(dir)
|
12
|
+
@countries = load_data(dir, "countries/features.geo.json")
|
13
|
+
@administrative_areas_level_1 = Hash.new do |h,k|
|
14
|
+
h[k] = load_data(File.join(dir, "countries", k), "features.geo.json")
|
15
|
+
end
|
16
|
+
@administrative_areas_level_2 = Hash.new do |h,k|
|
17
|
+
h[k] = load_data(File.join(dir, "countries", *k), "features.geo.json")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def countries
|
22
|
+
@countries
|
23
|
+
end
|
24
|
+
|
25
|
+
def administrative_areas_level_1(country_id)
|
26
|
+
return nil if country_id.nil?
|
27
|
+
@administrative_areas_level_1[country_id]
|
28
|
+
end
|
29
|
+
|
30
|
+
def administrative_areas_level_2(country_id, aa1_id)
|
31
|
+
return nil if country_id.nil? || aa1_id.nil?
|
32
|
+
@administrative_areas_level_2[[country_id, aa1_id]]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def load_data(dir, file)
|
38
|
+
features = JSON.load(File.open(File.join(dir, file)))['features']
|
39
|
+
features.map do |f|
|
40
|
+
id = f['id'] ? f['id'][/\w+$/] : ""
|
41
|
+
|
42
|
+
# Note: Perimeter is always first element in GeoJSON.
|
43
|
+
geometries = case f['geometry']['type']
|
44
|
+
when "MultiPolygon"
|
45
|
+
f['geometry']['coordinates'].map { |g| Geometry::Polygon.from_point_array(g.first) }
|
46
|
+
when "Polygon"
|
47
|
+
Array(Geometry::Polygon.from_point_array(f['geometry']['coordinates'].first))
|
48
|
+
else
|
49
|
+
raise "Don't know how to handle geometry type: #{f['geometry']['type']}"
|
50
|
+
end
|
51
|
+
Entity.new(id, f['properties']['name'], geometries)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module LocalGeocoder
|
2
|
+
|
3
|
+
Result = Struct.new(:country, :administrative_area_level_1, :administrative_area_level_2) do
|
4
|
+
alias_method :state, :administrative_area_level_1
|
5
|
+
alias_method :county, :administrative_area_level_2
|
6
|
+
def inspect
|
7
|
+
"#{self.country.inspect}, #{self.administrative_area_level_1.inspect}, #{self.administrative_area_level_2.inspect}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Geocoder
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@data_source = DataSource.new(DATA_DIR)
|
15
|
+
end
|
16
|
+
|
17
|
+
def reverse_geocode(lng, lat)
|
18
|
+
country = find_result(lng, lat)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def find_result(lng, lat)
|
24
|
+
cnt = find_country(lng, lat)
|
25
|
+
aa1, aa2 = find_administrative_areas(cnt.short_name, lng, lat) if cnt && cnt.short_name == "USA"
|
26
|
+
Result.new(cnt, aa1, aa2)
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_country(lng, lat)
|
30
|
+
@data_source.countries.find { |c| contains_location?(c, lng, lat); }
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_administrative_areas(country_id, lng, lat)
|
34
|
+
aa1 = @data_source.administrative_areas_level_1(country_id).find { |a| contains_location?(a, lng, lat) }
|
35
|
+
return nil,nil if aa1.nil?
|
36
|
+
|
37
|
+
aa2 = @data_source.administrative_areas_level_2(country_id, aa1.short_name).find { |a| contains_location?(a, lng, lat) }
|
38
|
+
return aa1, aa2
|
39
|
+
end
|
40
|
+
|
41
|
+
def contains_location?(entity, lng, lat)
|
42
|
+
entity.geometries.any? { |g| g.contains_point?(Geometry::Point.new(lng, lat)) }
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module LocalGeocoder
|
2
|
+
module Geometry
|
3
|
+
|
4
|
+
class Polygon
|
5
|
+
attr_accessor :points
|
6
|
+
|
7
|
+
def initialize(points)
|
8
|
+
@points = points
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.from_point_array(points)
|
12
|
+
Polygon.new(points.map { |p| Point.new(*p) })
|
13
|
+
end
|
14
|
+
|
15
|
+
def number_of_points
|
16
|
+
@points.size
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](i)
|
20
|
+
@points[i]
|
21
|
+
end
|
22
|
+
|
23
|
+
def bounding_box
|
24
|
+
@bounding_box ||= begin
|
25
|
+
min_x, max_x = @points.minmax_by { |p| p.x }.map { |p| p.x }
|
26
|
+
min_y, max_y = @points.minmax_by { |p| p.y }.map { |p| p.y }
|
27
|
+
Rect.new(min_x, min_y, max_x-min_x, max_y-min_y)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def contains_point?(point)
|
32
|
+
return false if !self.bounding_box.contains_point?(point)
|
33
|
+
|
34
|
+
contains_point = false
|
35
|
+
i = -1
|
36
|
+
j = self.number_of_points - 1
|
37
|
+
while (i += 1) < self.number_of_points
|
38
|
+
p1 = self[i]; p2 = self[j]
|
39
|
+
if within_y_bounds?(point, p1, p2)
|
40
|
+
if intersects_line_segment?(point, p1, p2)
|
41
|
+
contains_point = !contains_point
|
42
|
+
end
|
43
|
+
end
|
44
|
+
j = i
|
45
|
+
end
|
46
|
+
return contains_point
|
47
|
+
end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
@points.map { |p| p.inspect }.join(",")
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def within_y_bounds?(point, p1, p2)
|
56
|
+
(p1.y <= point.y && point.y < p2.y) ||
|
57
|
+
(p2.y <= point.y && point.y < p1.y)
|
58
|
+
end
|
59
|
+
|
60
|
+
def intersects_line_segment?(point, p1, p2)
|
61
|
+
(point.x < (p2.x - p1.x) * (point.y - p1.y) /
|
62
|
+
(p2.y - p1.y) + p1.x)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module LocalGeocoder
|
2
|
+
module Geometry
|
3
|
+
|
4
|
+
Rect = Struct.new(:x, :y, :width, :height) do
|
5
|
+
def contains_point?(point)
|
6
|
+
point.x >= self.x && point.y >= self.y &&
|
7
|
+
point.x <= (self.x+self.width) && point.y <= (self.y+self.height)
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(rect)
|
11
|
+
self.x == rect.x && self.y == rect.y &&
|
12
|
+
self.width == rect.width && self.height == rect.height
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"[#{x},#{y}],[#{x+width},#{y+height}]"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: local-geocoder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aish Fenton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: trollop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake-compiler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
executables:
|
72
|
+
- local_geocode
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ext/geometry/GeometryService.java
|
77
|
+
- ext/geometry/Point.java
|
78
|
+
- ext/geometry/Polygon.java
|
79
|
+
- ext/geometry/Rect.java
|
80
|
+
- ext/geometry/RPoint.java
|
81
|
+
- ext/geometry/RPolygon.java
|
82
|
+
- ext/geometry/RRect.java
|
83
|
+
- ext/geometry/extconf.rb
|
84
|
+
- lib/local_geocoder/data_source.rb
|
85
|
+
- lib/local_geocoder/geocoder.rb
|
86
|
+
- lib/local_geocoder/geometry/point.rb
|
87
|
+
- lib/local_geocoder/geometry/polygon.rb
|
88
|
+
- lib/local_geocoder/geometry/rect.rb
|
89
|
+
- lib/local_geocoder/version.rb
|
90
|
+
- lib/local_geocoder.rb
|
91
|
+
- bin/local_geocode
|
92
|
+
homepage:
|
93
|
+
licenses: []
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.0.3
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: Reverse geocodes lng, lat pairs into country codes (plus State and Counties
|
115
|
+
within the US). Runs locally, with no external dependancies, and is fast enough
|
116
|
+
for large batch jobs
|
117
|
+
test_files: []
|