Skip to content
This repository has been archived by the owner on Aug 23, 2022. It is now read-only.

WIP: Proposal for internal restructuring #8

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .travis.yml

This file was deleted.

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

### CI

We used Travis as our CI build tool. With the migration to Github we deleted the file. On this page it is described what Travis does and how it is specified (https://docs.travis-ci.com/user/languages/ruby/). If the project gets under active development you can use this to rebuild the CI pipeline.

### Running the demo

This repository includes a demo server which helps visualizing the results of the different actions provided by GeoFaker.
Expand All @@ -73,4 +77,3 @@ The gem is available as open source under the terms of the [MIT License](https:/
## Code of Conduct

Everyone interacting in the GeoFaker project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/geo_faker/blob/master/CODE_OF_CONDUCT.md).

89 changes: 22 additions & 67 deletions lib/geo_faker.rb
Original file line number Diff line number Diff line change
@@ -1,44 +1,13 @@
require 'geo_faker/version'
require 'geo_faker/geo_transform'
require 'geo_faker/polygon_with_holes'
require 'geo_faker/multi_polygon'
require 'geo_faker/point'
require 'rest-client'
require 'json'
require 'pry'
require 'geo_faker/geo_coding/coder'
require 'geo_faker/geometry/geo_transform'
require 'geo_faker/geometry/polygon_with_holes'
require 'geo_faker/geometry/multi_polygon'
require 'geo_faker/geometry/point'

module GeoFaker
BASE_URL = 'https://nominatim.openstreetmap.org/search'

@@geo_data = {}

def self.geo_data(query, with_polygon: false)
@@geo_data[query] ||= load_geo_data(query, with_polygon: with_polygon)
if with_polygon && !@@geo_data[query].key?('geojson')
@@geo_data[query] = load_geo_data(query, with_polygon: with_polygon)
end
@@geo_data[query]
end

def self.load_geo_data(query, with_polygon: false)
response = RestClient.get(BASE_URL, params: {
q: query,
format: 'json',
limit: 1,
polygon_geojson: with_polygon ? '1' : '0',
})

raise "API error: #{response.code}" unless response.code == 200

data = JSON.parse(response.body)
raise "No matching result." if data.empty?
data.first
end

def self.around(query, radius_in_km:)
data = geo_data(query)
lat = data['lat'].to_f
lon = data['lon'].to_f
result = GeoCoding::Coder.geo_data(query)

angle = 2 * Math::PI * rand()
distance = nil
Expand All @@ -47,12 +16,12 @@ def self.around(query, radius_in_km:)
break if distance.abs < 3 * radius_in_km
end

delta_lat = GeoTransform.km_to_degree_lat(distance * Math.cos(angle))
delta_lon = GeoTransform.km_to_degree_lon(distance * Math.sin(angle), lat)
delta_lat = Geometry::GeoTransform.km_to_degree_lat(distance * Math.cos(angle))
delta_lon = Geometry::GeoTransform.km_to_degree_lon(distance * Math.sin(angle), result.center.lat)

Point.new(
lat: lat + delta_lat,
lon: lon + delta_lon,
Geometry::Point.new(
lat: result.center.lat + delta_lat,
lon: result.center.lon + delta_lon,
)
end

Expand All @@ -65,40 +34,26 @@ def self.gaussian_rand
end

def self.within_bounds(query)
data = geo_data(query)
bounds = data['boundingbox'].map(&:to_f)
result = GeoCoding::Coder.geo_data(query)
bounds = result.bounding_box

south = bounds[0]
north = bounds[1]
west = bounds[2]
east = bounds[3]

Point.new(
lat: rand(south..north),
lon: rand(west..east),
Geometry::Point.new(
lat: rand(bounds.south..bounds.north),
lon: rand(bounds.west..bounds.east),
)
end

def self.within(query)
data = geo_data(query, with_polygon: true)

bounds = data['boundingbox'].map(&:to_f)
south = bounds[0]
north = bounds[1]
west = bounds[2]
east = bounds[3]

geojson = data['geojson']
raise 'geojson must be either Polygon or MultiPolygon' unless ['Polygon', 'MultiPolygon'].include?(geojson['type'])
multi_polygon = MultiPolygon.from_geojson(geojson)
result = GeoCoding::Coder.geo_data(query, with_polygon: true)
bounds = result.bounding_box

loop do
point = Point.new(
lat: rand(south..north),
lon: rand(west..east),
point = Geometry::Point.new(
lat: rand(bounds.south..bounds.north),
lon: rand(bounds.west..bounds.east),
)

return point if multi_polygon.contains_point?(point)
return point if result.bounding_polygon.contains_point?(point)
end
end
end
68 changes: 68 additions & 0 deletions lib/geo_faker/geo_coding/cached_nominatim_coder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
require 'geo_faker/geo_coding/result'
require 'geo_faker/geometry/point'
require 'geo_faker/geometry/bounding_box'
require 'geo_faker/geometry/multi_polygon'
require 'rest-client'

module GeoFaker
module GeoCoding
module CachedNominatimCoder
BASE_URL = 'https://nominatim.openstreetmap.org/search'

@@geo_data = {}

def self.geo_data(query, with_polygon: false)
@@geo_data[query] ||= load_geo_data(query, with_polygon: with_polygon)
if with_polygon && @@geo_data[query].bounding_polygon == nil
@@geo_data[query] = load_geo_data(query, with_polygon: with_polygon)
end
@@geo_data[query]
end

private

def self.load_geo_data(query, with_polygon: false)
response = RestClient.get(BASE_URL, params: {
q: query,
format: 'json',
limit: 1,
polygon_geojson: with_polygon ? '1' : '0',
})

raise "API error: #{response.code}" unless response.code == 200

data = JSON.parse(response.body)
raise "No matching result." if data.empty?
parse_result(data.first)

end

def self.parse_result(json)
Result.new(
center: Geometry::Point.new(lat: json['lat'].to_f, lon: json['lon'].to_f),
bounding_box: parse_bounding_box(json),
bounding_polygon: parse_bounding_polygon(json),
)
end

def self.parse_bounding_box(json)
bounds = json['boundingbox'].map(&:to_f)

Geometry::BoundingBox.new(
south: bounds[0],
north: bounds[1],
west: bounds[2],
east: bounds[3],
)
end

def self.parse_bounding_polygon(json)
if json.key?('geojson')
Geometry::MultiPolygon.from_geojson(json['geojson'])
else
nil
end
end
end
end
end
7 changes: 7 additions & 0 deletions lib/geo_faker/geo_coding/coder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'geo_faker/geo_coding/cached_nominatim_coder'

module GeoFaker
module GeoCoding
Coder = CachedNominatimCoder
end
end
15 changes: 15 additions & 0 deletions lib/geo_faker/geo_coding/result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module GeoFaker
module GeoCoding
class Result
attr_reader :center
attr_reader :bounding_box
attr_reader :bounding_polygon

def initialize(center:, bounding_box:, bounding_polygon:)
@center = center
@bounding_box = bounding_box
@bounding_polygon = bounding_polygon
end
end
end
end
14 changes: 0 additions & 14 deletions lib/geo_faker/geo_transform.rb

This file was deleted.

14 changes: 14 additions & 0 deletions lib/geo_faker/geometry/bounding_box.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module GeoFaker
module Geometry
class BoundingBox
attr_reader :north, :east, :south, :west

def initialize(north:, east:, south:, west:)
@north = north
@east = east
@south = south
@west = west
end
end
end
end
16 changes: 16 additions & 0 deletions lib/geo_faker/geometry/geo_transform.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module GeoFaker
module Geometry
class GeoTransform
KM_PER_DEGREE_LAT = 110.574
KM_PER_DEGREE_LON_AT_EQUATOR = 111.320

def self.km_to_degree_lat(delta_km)
delta_km / KM_PER_DEGREE_LAT
end

def self.km_to_degree_lon(delta_km, lat)
delta_km / (KM_PER_DEGREE_LON_AT_EQUATOR * Math.cos(lat * Math::PI / 180))
end
end
end
end
26 changes: 26 additions & 0 deletions lib/geo_faker/geometry/multi_polygon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module GeoFaker
module Geometry
class MultiPolygon
attr_reader :polygons

def self.from_geojson(geojson)
case geojson.fetch('type')
when 'MultiPolygon'
MultiPolygon.new(geojson.fetch('coordinates'))
when 'Polygon'
MultiPolygon.new([geojson.fetch('coordinates')])
end
end

def contains_point?(point)
polygons.any? {|polygon_with_holes| polygon_with_holes.contains_point?(point) }
end

private

def initialize(polygons)
@polygons = polygons.map {|polygon_with_holes| PolygonWithHoles.new(polygon_with_holes) }
end
end
end
end
19 changes: 19 additions & 0 deletions lib/geo_faker/geometry/point.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module GeoFaker
module Geometry
class Point
attr_reader :lat, :lon

def initialize(lat:, lon:)
@lat = lat
@lon = lon
end

def to_json(*opts)
{
lat: lat,
lon: lon,
}.to_json(opts)
end
end
end
end
41 changes: 41 additions & 0 deletions lib/geo_faker/geometry/polygon_with_holes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module GeoFaker
module Geometry
class PolygonWithHoles
attr_reader :outer_polygon, :inner_polygons

def initialize(polygon_with_holes)
@outer_polygon = polygon_with_holes[0]
@inner_polygons = polygon_with_holes.slice(1..-1)
end

def contains_point?(point)
point_in_polygon(outer_polygon, point) &&
inner_polygons.none? {|inner_polygon| point_in_polygon(inner_polygon, point) }
end

private

def point_in_polygon(polygon, point)
point_in_polygon = false

last_point = polygon[-1]
y = point.lon.to_f
x = point.lat.to_f

polygon.each do |p|
yi = p[0]
xi = p[1]
yj = last_point[0]
xj = last_point[1]
if yi < y && yj >= y ||
yj < y && yi >= y
point_in_polygon = !point_in_polygon if xi + (y - yi) / (yj - yi) * (xj - xi) < x
end
last_point = p
end

point_in_polygon
end
end
end
end
24 changes: 0 additions & 24 deletions lib/geo_faker/multi_polygon.rb

This file was deleted.

Loading