cityway-transdev/activeroad

View on GitHub
app/models/active_road/osm_pbf_importer.rb

Summary

Maintainability
C
7 hrs
Test Coverage
module ActiveRoad
  module OsmPbfImporter

    @@relation_required_tags_keys = ["boundary", "admin_level"]
    @@relation_selected_tags_keys = ["boundary", "admin_level", "ref:INSEE", "name", "addr:postcode", "type"]
    mattr_reader :relation_required_tags_keys
    mattr_reader :relation_selected_tags_keys    

    @@way_required_tags_keys = ["highway", "railway", "boundary", "admin_level", "addr:housenumber"]
    @@way_for_physical_road_required_tags_keys = ["highway", "railway"]
    @@way_for_boundary_required_tags_keys = ["boundary", "admin_level"]
    @@way_for_street_number_required_tags_keys = ["addr:housenumber"]
    @@way_selected_tags_keys = [ "name", "maxspeed", "oneway", "boundary", "admin_level", "addr:housenumber" ]
    # Add first_node_id and last_node_id
    @@way_optionnal_tags_keys = ["highway", "railway", "maxspeed", "bridge", "tunnel", "toll", "cycleway", "cycleway-right", "cycleway-left", "cycleway-both", "oneway:bicycle", "oneway", "bicycle", "segregated", "foot", "lanes", "lanes:forward", "lanes:forward:bus", "busway:right", "busway:left", "oneway_bus", "boundary", "admin_level", "access", "construction", "junction", "motor_vehicle", "psv", "bus", "addr:city", "addr:country", "addr:state", "addr:street"]
    mattr_reader :way_required_tags_keys
    mattr_reader :way_for_physical_road_required_tags_keys
    mattr_reader :way_for_boundary_required_tags_keys
    mattr_reader :way_for_street_number_required_tags_keys
    mattr_reader :way_selected_tags_keys
    mattr_reader :way_optionnal_tags_keys

    @@nodes_selected_tags_keys = [ "addr:housenumber", "addr:city", "addr:postcode", "addr:street" ]
    mattr_reader :nodes_selected_tags_keys

    @@pg_batch_size = 10000 # Not Rails.logger.debug a high value because postgres failed to allocate memory
    mattr_reader :pg_batch_size

    def self.included(base)
      base.extend(ClassMethods)
    end

    module ClassMethods
    end

    def pedestrian?(tags)
      highway_tag_values = %w{pedestrian footway path steps}
      if tags["highway"].present? && highway_tag_values.include?(tags["highway"])
        true
      else
        false
      end
    end

    # http://wiki.openstreetmap.org/wiki/FR:Cycleway
    # http://wiki.openstreetmap.org/wiki/FR:Bicycle
    def bike?(tags)
      highway_tag_values = %w{cycleway}
      bike_tags_keys = ["cycleway:left", "cycleway:right", "cycleway", "cycleway:left"]

      if (tags["highway"].present? && highway_tag_values.include?(tags["highway"])) || (bike_tags_keys & tags.keys).present?
        true
      else
        false
      end
    end

    # http://wiki.openstreetmap.org/wiki/Key:railway
    def train?(tags)
      railway_tag_values = %w{rail tram funicular light_rail subway}
      if tags["railway"].present? && railway_tag_values.include?(tags["railway"])
        true
      else
        false
      end
    end

    # http://wiki.openstreetmap.org/wiki/FR:France_roads_tagging
    def car?(tags)
      highway_tag_values = %w{motorway trunk trunk_link primary secondary tertiary motorway_link primary_link unclassified service road residential track}
      if tags["highway"].present? && highway_tag_values.include?(tags["highway"])
        true
      else
        false
      end
    end

    def required_way?(required_tags, tags)
      required_tags.each do |require_tag_key|
        if tags.keys.include?(require_tag_key)
          return true
        end
      end
      return false
    end

    def required_relation?(tags)
      @@relation_required_tags_keys.each do |require_tag_key|
        if tags.keys.include?(require_tag_key)
          return true
        end
      end
      return false
    end

    # Return an hash with tag_key => tag_value for osm attributes
    def selected_tags(tags, selected_tags_keys)
      {}.tap do |selected_tags|
        tags.each do |key, value|
          if selected_tags_keys.include?(key)
            selected_tags[key] = value
          end
        end           
      end
    end

    # def extract_tag_value(tag_value)
    #   case tag_value 
    #   when "yes" : 1 
    #   when "no" : 0
    #   when /[0-9].+/i tag_value.to_f        
    #   else 0    
    #   end     
    # end

    def physical_road_conditionnal_costs(way)
      [].tap do |prcc|        
        prcc << [ "car", Float::MAX] if !way.car
        prcc << [ "pedestrian", Float::MAX] if !way.pedestrian
        prcc << [ "bike", Float::MAX] if !way.bike
        prcc << [ "train", Float::MAX] if !way.train
      end
    end            

    def extract_relation_polygon(outer_geometries, inner_geometries = [])
      outer_rings = join_ways(outer_geometries)
      inner_rings = join_ways(inner_geometries)

      # TODO : Fix the case where many outer rings with many inner rings
      polygons = [].tap do |polygons|
        outer_rings.each { |outer_ring|
          polygons << GeoRuby::SimpleFeatures::Polygon.from_linear_rings( [outer_ring] + inner_rings )
        }
      end
    end

    def join_ways(ways)
      closed_ways = []
      endpoints_to_ways = EndpointToWayMap.new
      for way in ways
        if way.closed?
          closed_ways << way
          next
        end

        # Are there any existing ways we can join this to?
        to_join_to = endpoints_to_ways.get_from_either_end(way)
        if to_join_to.present?
          joined = way
          for existing_way in to_join_to
            joined = join_way(joined, existing_way)
            endpoints_to_ways.remove_way(existing_way)
            if joined.closed?
              closed_ways << joined
              break
            end
          end

          if !joined.closed?
            endpoints_to_ways.add_way(joined)
          end
        else
          endpoints_to_ways.add_way(way)
        end
      end

      if endpoints_to_ways.number_of_endpoints != 0
        raise StandardError, "Unclosed boundaries"
      end

      closed_ways
    end

    def join_way(way, other)
      if way.closed?
        raise StandardError, "Trying to join a closed way to another"
      end
      if other.closed?
        raise StandardError, "Trying to join a way to a closed way"
      end

      if way.points.first == other.points.first
        new_points = other.reverse.points[0..-2] + way.points
      elsif way.points.first == other.points.last
        new_points = other.points[0..-2] + way.points
      elsif way.points.last == other.points.first
        new_points = way.points[0..-2] + other.points
      elsif way.points.last == other.points.last
        new_points = way.points[0..-2] + other.reverse.points
      else
        raise StandardError, "Trying to join two ways with no end point in common"
      end

      GeoRuby::SimpleFeatures::LineString.from_points(new_points)
    end

    class EndpointToWayMap
      attr_accessor :endpoints

      def initialize
        @endpoints = {}
      end

      def add_way(way)
        if get_from_either_end(way).present?
          raise StandardError, "Call to add_way would overwrite existing way(s)"
        end
        self.endpoints[way.points.first] = way
        self.endpoints[way.points.last] = way
      end

      def remove_way(way)
        endpoints.delete(way.points.first)
        endpoints.delete(way.points.last)
      end

      def get_from_either_end(way)
        [].tap do |selected_end_points|
          selected_end_points << endpoints[way.points.first] if endpoints.include?(way.points.first)
          selected_end_points << endpoints[way.points.last] if endpoints.include?(way.points.last)
        end
      end

      def number_of_endpoints
        return endpoints.size
      end

    end

    class Node
      attr_accessor :id, :lon, :lat, :ways, :end_of_way, :addr_housenumber, :tags

      def initialize(id, lon, lat, addr_housenumber = "", ways = [], end_of_way = false, tags = {} )
        @id = id
        @lon = lon
        @lat = lat
        @addr_housenumber = addr_housenumber
        @ways = ways
        @end_of_way = end_of_way
        @tags = tags
      end

      def add_way(id)
        @ways << id
      end

      def marshal_dump
        [@id, @lon, @lat, @addr_housenumber, @ways, @end_of_way, @tags]
      end

      def marshal_load array
        @id, @lon, @lat, @addr_housenumber, @ways, @end_of_way, @tags = array
      end

      def used?
        ( ways.present? && ways.size > 1 ) || end_of_way
      end
    end

    class Way
      attr_accessor :id, :nodes, :car, :bike, :train, :pedestrian, :name, :maxspeed, :oneway, :boundary, :admin_level, :addr_housenumber, :options

      def initialize(id, nodes = [], car = false, bike = false, train = false, pedestrian = false, name = "", maxspeed = 0, oneway = false, boundary = "", admin_level = "", addr_housenumber = "", options = {})
        @id = id
        @nodes = nodes
        @car = car
        @bike = bike
        @train = train
        @pedestrian = pedestrian
        @name = name
        @maxspeed = maxspeed
        @oneway = oneway
        @boundary = boundary
        @admin_level = admin_level
        @addr_housenumber = addr_housenumber
        @options = options
      end

      def add_node(id)
        @nodes << id
      end

      def marshal_dump
        [@id, @nodes, @car, @bike, @train, @pedestrian, @name, @maxspeed, @oneway, @boundary, @admin_level, @addr_housenumber, @options]
      end

      def marshal_load array
        @id, @nodes, @car, @bike, @train, @pedestrian, @name, @maxspeed, @oneway, @boundary, @admin_level, @addr_housenumber, @options = array
      end
    end

  end
end