local-geocoder 0.1.1
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 +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: []
|