[OSRM] 커스트마이징 경로 탐색 - 4 : OSRM Profile 생성하기

ZEDY·2024년 2월 11일

만약 데이터가 있다면, 그 데이터를 기반으로 가중치를 주고 회피하는 알고리즘을 짜서 길을 생성하는 거를 해보겠다.

https://wiki.openstreetmap.org/wiki/Wheelchair_routing

비슷한 프로젝트 발견.

우선 OSRM을 하기 위해서는, 나만의 새로운 길을 만들기 위해서는 Profile을 만들어야한다.
나같은 경우는 safest / foot 이 되겠다.

https://github.com/Project-OSRM/osrm-backend/blob/master/docs/profiles.md

아.. 문서를 읽을 수록 영어 공부를 해야겠다는 생각이 가득찬다.
그래도 내가 해내면 다른 개발자들이 내 고군분투를 보며 편하게 개발할 수 있겠지..


Profile 설정

A profile describes whether or not it's possible to route along a particular type of way, whether we can pass a particular node, and how quickly we'll be traveling when we do. This feeds into the way the routing graph is created and thus influences the output routes.
-> 프로필은 특정 유형의 길을 따라 경로를 지정하는 것이 가능한지, 특정 노드를 통과할 수 있는지, 통과할 때 얼마나 빨리 이동할 수 있는지를 설명한다. 이는 라우팅 그래프가 생성되는 방식에 반영되어 출력 경로에 영향을 미친다.

Profiles have a 'lua' extension, and are placed in 'profiles' directory.
-> 프로필은 프로필 폴더에 위치하고 확장자가 lua 이다. 나중에 다른 유저가 이걸로 라우팅을 하고 싶을 때 사용하는 거 같다.

이 프로필을 사용하기 위해서는 OSM 데이터를 전처리하는 흐름을 이해해야한다.

OSM 데이터 전처리 흐름

Settings: .lua file
Input: Raw OSM data. (.xml or .pbf file)
Output: Intermediate OSRM format. (.osrm and .names files)

여기서 프로필을 설정해놓으면, 길이 Raw data로 들어오고, 그러면 아웃풋으로 경로가 나가는 흐름이다.

프로필은 lua 스크립트 언어로 작성해놨다고 함. 어려워보이네.

근데 막상 foot.lua 를 보니까 그렇게 어렵지는 않은거 같다.

foot.lua 분석해보기

-- Foot profile

api_version = 2

Set = require('lib/set')
Sequence = require('lib/sequence')
Handlers = require("lib/way_handlers")
find_access_tag = require("lib/access").find_access_tag

function setup()
  local walking_speed = 5
  return {
    properties = {
      weight_name                   = 'duration',
      max_speed_for_map_matching    = 40/3.6, -- kmph -> m/s
      call_tagless_node_function    = false,
      traffic_light_penalty         = 2,
      u_turn_penalty                = 2,
      continue_straight_at_waypoint = false,
      use_turn_restrictions         = false,
    },

    default_mode            = mode.walking,
    default_speed           = walking_speed,
    oneway_handling         = 'specific',     -- respect 'oneway:foot' but not 'oneway'

    barrier_blacklist = Set {
      'yes',
      'wall',
      'fence'
    },

    access_tag_whitelist = Set {
      'yes',
      'foot',
      'permissive',
      'designated'
    },

    access_tag_blacklist = Set {
      'no',
      'agricultural',
      'forestry',
      'private',
      'delivery',
    },

    restricted_access_tag_list = Set { },

    restricted_highway_whitelist = Set { },

    construction_whitelist = Set {},

    access_tags_hierarchy = Sequence {
      'foot',
      'access'
    },

    -- tags disallow access to in combination with highway=service
    service_access_tag_blacklist = Set { },

    restrictions = Sequence {
      'foot'
    },

    -- list of suffixes to suppress in name change instructions
    suffix_list = Set {
      'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'North', 'South', 'West', 'East'
    },

    avoid = Set {
      'impassable',
      'proposed'
    },

    speeds = Sequence {
      highway = {
        primary         = walking_speed,
        primary_link    = walking_speed,
        secondary       = walking_speed,
        secondary_link  = walking_speed,
        tertiary        = walking_speed,
        tertiary_link   = walking_speed,
        unclassified    = walking_speed,
        residential     = walking_speed,
        road            = walking_speed,
        living_street   = walking_speed,
        service         = walking_speed,
        track           = walking_speed,
        path            = walking_speed,
        steps           = walking_speed,
        pedestrian      = walking_speed,
        footway         = walking_speed,
        pier            = walking_speed,
      },

      railway = {
        platform        = walking_speed
      },

      amenity = {
        parking         = walking_speed,
        parking_entrance= walking_speed
      },

      man_made = {
        pier            = walking_speed
      },

      leisure = {
        track           = walking_speed
      }
    },

    route_speeds = {
      ferry = 5
    },

    bridge_speeds = {
    },

    surface_speeds = {
      fine_gravel =   walking_speed*0.75,
      gravel =        walking_speed*0.75,
      pebblestone =   walking_speed*0.75,
      mud =           walking_speed*0.5,
      sand =          walking_speed*0.5
    },

    tracktype_speeds = {
    },

    smoothness_speeds = {
    }
  }
end

function process_node(profile, node, result)
  -- parse access and barrier tags
  local access = find_access_tag(node, profile.access_tags_hierarchy)
  if access then
    if profile.access_tag_blacklist[access] then
      result.barrier = true
    end
  else
    local barrier = node:get_value_by_key("barrier")
    if barrier then
      --  make an exception for rising bollard barriers
      local bollard = node:get_value_by_key("bollard")
      local rising_bollard = bollard and "rising" == bollard

      if profile.barrier_blacklist[barrier] and not rising_bollard then
        result.barrier = true
      end
    end
  end

  -- check if node is a traffic light
  local tag = node:get_value_by_key("highway")
  if "traffic_signals" == tag then
    -- Direction should only apply to vehicles
    result.traffic_lights = true
  end
end

-- main entry point for processsing a way
function process_way(profile, way, result)
  -- the intial filtering of ways based on presence of tags
  -- affects processing times significantly, because all ways
  -- have to be checked.
  -- to increase performance, prefetching and intial tag check
  -- is done in directly instead of via a handler.

  -- in general we should  try to abort as soon as
  -- possible if the way is not routable, to avoid doing
  -- unnecessary work. this implies we should check things that
  -- commonly forbids access early, and handle edge cases later.

  -- data table for storing intermediate values during processing
  local data = {
    -- prefetch tags
    highway = way:get_value_by_key('highway'),
    bridge = way:get_value_by_key('bridge'),
    route = way:get_value_by_key('route'),
    leisure = way:get_value_by_key('leisure'),
    man_made = way:get_value_by_key('man_made'),
    railway = way:get_value_by_key('railway'),
    platform = way:get_value_by_key('platform'),
    amenity = way:get_value_by_key('amenity'),
    public_transport = way:get_value_by_key('public_transport')
  }

  -- perform an quick initial check and abort if the way is
  -- obviously not routable. here we require at least one
  -- of the prefetched tags to be present, ie. the data table
  -- cannot be empty
  if next(data) == nil then     -- is the data table empty?
    return
  end

  local handlers = Sequence {
    -- set the default mode for this profile. if can be changed later
    -- in case it turns we're e.g. on a ferry
    WayHandlers.default_mode,

    -- check various tags that could indicate that the way is not
    -- routable. this includes things like status=impassable,
    -- toll=yes and oneway=reversible
    WayHandlers.blocked_ways,

    -- determine access status by checking our hierarchy of
    -- access tags, e.g: motorcar, motor_vehicle, vehicle
    WayHandlers.access,

    -- check whether forward/backward directons are routable
    WayHandlers.oneway,

    -- check whether forward/backward directons are routable
    WayHandlers.destinations,

    -- check whether we're using a special transport mode
    WayHandlers.ferries,
    WayHandlers.movables,

    -- compute speed taking into account way type, maxspeed tags, etc.
    WayHandlers.speed,
    WayHandlers.surface,

    -- handle turn lanes and road classification, used for guidance
    WayHandlers.classification,

    -- handle various other flags
    WayHandlers.roundabouts,
    WayHandlers.startpoint,

    -- set name, ref and pronunciation
    WayHandlers.names,

    -- set weight properties of the way
    WayHandlers.weights
  }

  WayHandlers.run(profile, way, result, data, handlers)
end

function process_turn (profile, turn)
  turn.duration = 0.

  if turn.direction_modifier == direction_modifier.u_turn then
     turn.duration = turn.duration + profile.properties.u_turn_penalty
  end

  if turn.has_traffic_light then
     turn.duration = profile.properties.traffic_light_penalty
  end
  if profile.properties.weight_name == 'routability' then
      -- penalize turns from non-local access only segments onto local access only tags
      if not turn.source_restricted and turn.target_restricted then
          turn.weight = turn.weight + 3000
      end
  end
end

return {
  setup = setup,
  process_way =  process_way,
  process_node = process_node,
  process_turn = process_turn
}

이 코드는 지리 정보 시스템(GIS) 또는 매핑 응용 프로그램과 관련된 루아(Lua) 스크립트이다. 지금 OSRM의 도보 길찾기의 프로필이다.

  1. Setup 함수:

    • 라우팅 프로파일을 초기화하고 구성한다.
    • 'weight_name', 'max_speed_for_map_matching' 등과 같은 여러 속성을 설정한다.
    • 기본 걷기 속도 및 다양한 접근 관련 규칙을 정의한다.
  2. Process Node 함수:

    • 노드 속성(접근 및 장벽 태그 등)을 구문 분석하고 처리한다.
    • 노드가 신호등인지 여부를 결정한다.
  3. Process Way 함수:

    • 태그 및 속성에 기반한 방법(경로 또는 루트)을 처리한다.
    • 방법 속성과 관련된 여러 검사 및 계산을 수행하기 위해 일련의 핸들러를 사용한다.
    • 'highway', 'bridge', 'route' 등과 같은 태그를 검사하여 방법이 루트로 지정되었는지 확인한다.
    • 액세스, 단일 방향 상태, 속도, 표면 유형, 분류 및 기타 요인과 관련된 여러 핸들러를 사용한다.
    • 라우팅 결정을 위한 가중치 및 패널티를 계산한다.
    • 회전 제한, 로터리(원형 교차로), 시작 지점 처리 및 방법에 대한 이름, 참조 및 발음을 설정한다.
  4. Process Turn 함수:

    • 여러 요인에 기반한 회전 패널티를 계산합니다. U턴 및 신호등과 관련된 패널티를 부여한다.
    • 가중치 이름이 'routability'인 경우, 비로컬 액세스 전용 세그먼트에서 로컬 액세스 전용 태그로의 회전에 대한 패널티를 적용한다.
  5. 반환된 모듈:

    • setup, process_way, process_node, 및 process_turn 함수를 내보낸다.
    • 이러한 함수는 메인 응용 프로그램에서 라우팅 프로파일을 설정하고 노드, 방법, 및 회전을 처리하는 데 사용될 수 있다.

Profile 생성하기

https://github.com/Project-OSRM/osrm-backend/blob/master/docs/profiles.md

참고하였습니다.

내가 원하는 태그와 옵션, 스피드 등을 설정하여 새롭게 lua 파일을 커스텀하였다.
OSRM에 정말 많은 태그가 있는 것으로 아는데, 그 종류를 어떻게 하면 확인할 수 있는지 그리고 사용할 수 있는지 잘 모르겠다.
그래도 우선은 완성은 했다.
이제 Docker을 이용해 배포를 한 뒤, 잘 불러와 지는지 테스트를 해보겠다.


라우팅을 하는 것은 물론 처음 설계가 가장 중요하겠지만, 잘 모르겠을 때는 일단 부딫혀봐야 한다.
기존 라우팅과 다른 .. 조금 차별점이 있고 좋은 알고리즘이 반영되었으면 좋겠다.

profile
IT기획/운영

0개의 댓글