lib/georuby-ext/georuby/locators.rb
class GeoRuby::SimpleFeatures::MultiLineString
alias_method :lines, :geometries
def locate_point(target)
nearest_locator = nearest_locator(target)
nearest_locator.location + nearest_locator.index
end
def interpolate_point(location)
line_index, line_location = location.to_i, location % 1
if line = lines[line_index]
line.interpolate_point(line_location)
end
end
def nearest_locator(target)
locators(target).min_by(&:distance_from_line)
end
def locators(point)
[].tap do |locators|
lines.each_with_index do |line, index|
locators << PointLocator.new(point, line, index)
end
end
end
class PointLocator
attr_reader :target, :line, :index
def initialize(target, line, index)
@target = target
@line = line
@index = index
end
def location
line.locate_point(target)
end
def distance_on_line
line.distance_on_line(target)
end
def distance_from_line
line.distance_from_line(target)
end
end
end
class GeoRuby::SimpleFeatures::LineString
def locate_point(target)
distance_on_line(target) / spherical_distance
end
def distance_on_line(target)
nearest_locator = nearest_locator(target)
nearest_locator.distance_on_segment + nearest_locator.segment.line_distance_at_departure
end
def distance_from_line(target)
nearest_locator(target).distance_from_segment
end
def nearest_locator(target)
locators(target).min_by(&:distance_from_segment)
end
def locators(point)
segments.collect { |segment| segment.locator(point) }
end
def segments_without_cache
previous_point = nil
distance_from_departure = 0
points.inject([]) do |segments, point|
Segment.new(previous_point, point).tap do |segment|
segment.line = self
segment.line_distance_at_departure = distance_from_departure
distance_from_departure += segment.distance
segments << segment
end if previous_point
previous_point = point
segments
end
end
def segments_with_cache
@segments ||= segments_without_cache
end
alias_method :segments, :segments_with_cache
def interpolate_point(location)
return points.last if location >= 1
return points.first if location <= 0
distance_on_line = location * spherical_distance
segment = segments.find do |segment|
segment.line_distance_at_arrival > distance_on_line
end
location_on_segment =
(distance_on_line - segment.line_distance_at_departure) / segment.distance
segment.interpolate_point location_on_segment
end
class Segment
attr_reader :departure, :arrival
def initialize(departure, arrival)
@departure, @arrival = departure, arrival
end
attr_accessor :line, :line_distance_at_departure
def line_distance_at_arrival
line_distance_at_departure + distance
end
def locator(target)
PointLocator.new target, self
end
def location_in_line
if line and line_distance_at_departure
line_distance_at_departure / line.distance
end
end
def square_of_distance
distance**2
end
def distance
@distance ||= departure.spherical_distance(arrival)
end
def to_s
"#{departure.x},#{departure.y}..#{arrival.x},#{arrival.y}"
end
def interpolate_point(location)
dx, dy =
(arrival.x - departure.x)*location, (arrival.y - departure.y)*location
GeoRuby::SimpleFeatures::Point.from_x_y departure.x + dx, departure.y + dy, line.srid
end
end
class PointLocator
include Math
attr_reader :target, :segment
delegate :departure, :arrival, :to => :segment
def initialize(target, segment_or_departure, arrival = nil)
@segment =
if arrival
Segment.new(segment_or_departure, arrival)
else
segment_or_departure
end
@target = target
raise "Target is not defined" unless target
end
def distance_from_segment
return 0 if [segment.departure, segment.arrival].include?(target)
sin_angle * target_distance_from_departure
end
def distance_on_segment
# like cos_angle * target_distance_from_departure
# scalar_product / square_of_segment_distance
scalar_product / segment_distance
end
def locate_point
scalar_product / square_of_segment_distance
end
# def scalar_product
# (target.x-departure.x)*(arrival.x-departure.x) + (target.y-departure.y)*(arrival.y-departure.y).to_f
# end
def scalar_product
departure_target_metric_delta = departure.metric_delta(target)
departure_arrival_metric_delta = departure.metric_delta(arrival)
departure_target_metric_delta[0]*departure_arrival_metric_delta[0] +
departure_target_metric_delta[1]*departure_arrival_metric_delta[1]
end
def angle
acos cos_angle
end
def sin_angle
sin angle
end
def cos_angle
[-1, [1, scalar_product / segment_distance / target_distance_from_departure].min].max
end
def square_of_segment_distance
segment_distance ** 2
end
def segment_distance
segment.distance
end
def target_distance_from_departure
departure.spherical_distance target
end
end
end