Skip to content

Instantly share code, notes, and snippets.

@smellman
Created August 16, 2017 22:01
Show Gist options
  • Select an option

  • Save smellman/b6198065fd89fa9ac653b9ad48a40379 to your computer and use it in GitHub Desktop.

Select an option

Save smellman/b6198065fd89fa9ac653b9ad48a40379 to your computer and use it in GitHub Desktop.
Full model code of SOTM 2017 smellman's presentation.
class OutdoorRouting
include ActiveModel::Model
attr_reader :result, :source_optional_id, :target_optional_id
attr_accessor :source_lon, :source_lat, :target_lon, :target_lat, :search_type, :source_name, :target_name
validates :source_lon, :source_lat, :target_lon, :target_lat, :search_type, presence: true
def initialize(source_lon, source_lat, target_lon, target_lat, search_type, source_name, target_name, source_optional_id = nil, target_optional_id = nil)
@source_lon = source_lon
@source_lat = source_lat
@target_lon = target_lon
@target_lat = target_lat
@search_type = search_type
@source_name = source_name
@target_name = target_name
@source_optional_id = source_optional_id
@target_optional_id = target_optional_id
end
def get_routing(language, text)
if text
return get_routing_text(language)
else
return get_routing_map
end
end
def get_routing_map
text = false
calculate(text)
way = get_routing_wkt
first_position = get_first_position
last_position = get_last_position
return {
type: "outdoor",
floor_id: nil,
floor_name: nil,
source_name: @source_name,
target_name: @target_name,
first_position: first_position,
last_position: last_position,
way: way,
beacon: false,
beacon_message: nil,
beacon_uuid: nil,
beacon_major: nil,
beacon_minor: nil
}
end
def get_routing_text(language)
text = true
calculate(text)
texts = get_routing_texts(language)
return {
type: "outdoor",
floor_id: nil,
floor_name: nil,
source_name: @source_name,
target_name: @target_name,
texts: texts,
beacon: false,
beacon_message: nil,
beacon_uuid: nil,
beacon_major: nil,
beacon_minor: nil
}
end
def get_cost(text)
calculate(text)
if (text)
if @result.count == 0
return Float::INFINITY
end
return @result.inject(0.0){|sum, n| sum + n["cost"]}
end
cost = @result[0]["cost"]
if cost.nil?
return Float::INFINITY
end
return cost
end
def get_routing_wkt
@result[0]["geom_text"]
end
def get_routing_texts(language)
generate_builder
@builder.output(language)
end
def get_first_position
@result[0]["start_position"]
end
def get_last_position
@result[0]["end_position"]
end
private
def generate_builder
return if @builder
@builder = RoutingTextBuilder.new
@result.each_with_index do |r, index|
@builder.put(index, r["heading"], r["length_m"])
end
@builder.finish
end
def calculate(text)
return if @result
if @search_type == :pedestrain
@result = ActiveRecord::Base.connection.execute(routing_for_pedestrain(text))
elsif @search_type == :wheelchair
@result = ActiveRecord::Base.connection.execute(routing_for_wheelchair(text))
elsif @search_type == :blind_low_vision
@result = ActiveRecord::Base.connection.execute(routing_for_blind_low_vision(text))
end
end
def routing_for_pedestrain(text)
dijkstra_condition = <<-EOS
SELECT id, source, target, length_m as cost
FROM routing.ways
EOS
routing_sql(dijkstra_condition, text)
end
def routing_for_wheelchair(text)
dijkstra_condition = <<-EOS
SELECT
routing.ways.id, routing.ways.source, routing.ways.target,
routing.ways.length_m *
CASE
WHEN routing.osm_ways.tags ? 'incline' THEN 2
ELSE 1
END
as cost
FROM routing.ways
JOIN routing.osm_ways
ON (routing.ways.osm_id = routing.osm_ways.osm_id
AND
NOT routing.osm_ways.tags @> 'highway=>steps'
AND
NOT routing.osm_ways.tags @> 'highway=>path'
AND
NOT routing.osm_ways.tags @> 'surface=>wood'
AND
NOT routing.osm_ways.tags @> 'surface=>unpaved')
EOS
routing_sql(dijkstra_condition, text)
end
def routing_for_blind_low_vision(text)
dijkstra_condition = <<-EOS
SELECT
routing.ways.id, routing.ways.source, routing.ways.target,
routing.ways.length_m *
CASE
WHEN routing.osm_ways.tags @> 'highway=>steps' THEN 40
WHEN routing.osm_ways.tags @> 'tactile_paving=>yes' THEN 0.5
ELSE 2
END
as cost
FROM routing.ways
JOIN routing.osm_ways
ON (
routing.ways.osm_id = routing.osm_ways.osm_id
AND
(
routing.osm_ways.tags @> 'tactile_paving=>yes'
OR
(
routing.osm_ways.tags @> 'handrail:left=>yes'
OR
routing.osm_ways.tags @> 'handrail:right=>yes'
OR
routing.osm_ways.tags @> 'handrail=>left'
OR
routing.osm_ways.tags @> 'handrail=>right'
OR
routing.osm_ways.tags @> 'handrail=>yes'
OR
routing.osm_ways.tags @> 'handrail=>both'
)
)
)
EOS
routing_sql(dijkstra_condition, text)
end
def routing_sql(dijkstra_condition, text)
grouping_text = ""
text_query = ""
if text
text_query = <<-EOS
with_geom.osm_id as osm_id,
degrees(ST_azimuth(
ST_StartPoint(ST_MakeLine(route_geom)),
ST_EndPoint(ST_MakeLine(route_geom))
)) as heading,
routing.osm_ways.tags as tags,
max(with_geom.seq) as max_seq,
EOS
grouping_text = <<-EOS
JOIN routing.osm_ways
ON (with_geom.osm_id = routing.osm_ways.osm_id)
group by with_geom.osm_id, routing.osm_ways.tags
order by max_seq;
EOS
end
<<-EOS
WITH
dijkstra AS (
SELECT * FROM pgr_dijkstra(
$$#{dijkstra_condition}$$,
(SELECT id FROM routing.ways_vertices_pgr
ORDER BY the_geom <-> ST_SetSRID(ST_Point(#{source_lon},#{source_lat}),4326) LIMIT 1),
(SELECT id FROM routing.ways_vertices_pgr
ORDER BY the_geom <-> ST_SetSRID(ST_Point(#{target_lon},#{target_lat}),4326) LIMIT 1),
false)
),
with_geom AS (
SELECT dijkstra.seq, dijkstra.cost, routing.ways.name, routing.ways.osm_id,
CASE
WHEN dijkstra.node = routing.ways.source THEN the_geom
ELSE ST_Reverse(the_geom)
END AS route_geom
FROM dijkstra JOIN routing.ways
ON (edge = ways.id) ORDER BY seq
)
SELECT
sum(with_geom.cost) as cost,
ST_Length(ST_MakeLine(route_geom)::geography) as length_m,
ST_AsText(ST_MakeLine(route_geom)) as geom_text,
#{text_query}
ST_AsText(ST_StartPoint(ST_MakeLine(route_geom))) as start_position,
ST_AsText(ST_EndPoint(ST_MakeLine(route_geom))) as end_position
FROM with_geom
#{grouping_text}
EOS
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment