This commit is contained in:
Татьяна Фарбер 2025-12-06 13:26:35 +04:00
parent da66b6a3fb
commit 0084ecff0c
7 changed files with 396 additions and 2 deletions

View File

@ -1,3 +1,80 @@
# osrm
# OSRM (Open Source Routing Machine) API Lua SDK
SDK для Open Source Routing Machine (OSRM)
API: https://project-osrm.org/docs/v5.24.0/api/
## Example:
```
local api = require('osrm.api')
local json = require('cjson')
res,err = api.route({{13.428555,52.523219},{13.428545,52.523249}})
res,err = api.route({{13.428555,52.523219},{13.428545,52.523249}},'walking')
if not err then print(json.encode(res)) else print(json.encode(res)..err) end
```
## Profiles
- driving
- walking
- cycling
## Services
### Nearest service
Snaps a coordinate to the street network and returns the nearest n matches.
```
nearest(coords,profile,options)
```
### Route service
Finds the fastest route between coordinates in the supplied order.
```
route(coords,profile,options)
```
### Table service
Computes the duration of the fastest route between all pairs of supplied coordinates. Returns the durations or distances or both between the coordinate pairs. Note that the distances are not the shortest distance between two coordinates, but rather the distances of the fastest routes. Duration is in seconds and distances is in meters.
```
table(coords,profile,options)
```
### Match service
Map matching matches/snaps given GPS points to the road network in the most plausible way. Please note the request might result multiple sub-traces. Large jumps in the timestamps (> 60s) or improbable transitions lead to trace splits if a complete matching could not be found. The algorithm might not be able to match all points. Outliers are removed if they can not be matched successfully.
```
match(coords,profile,options)
```
### Trip service
The trip plugin solves the Traveling Salesman Problem using a greedy heuristic (farthest-insertion algorithm) for 10 or more waypoints and uses brute force for less than 10 waypoints. The returned path does not have to be the fastest path. As TSP is NP-hard it only returns an approximation. Note that all input coordinates have to be connected for the trip service to work.
```
trip(coords,profile,options)
```
### Tile service
This service generates Mapbox Vector Tiles that can be viewed with a vector-tile capable slippy-map viewer.
```
tile(x,y,zoom,profile)
```

52
config/config.lua Executable file
View File

@ -0,0 +1,52 @@
local json = require('cjson.safe')
local _M = {}
_M.data = {}
_M.comments = {}
_M.file = '' -- файл конфигурации
local key
function _M.read()
for line in io.lines(_M.file) do
key = string.match(line, '([%w_]+)::')
if (key) then
_M.data[key] = string.match(line, '::(.*) #')
_M.comments[key] = string.match(line, '#(.*)')
if string.find(_M.data[key],'%{%"') or string.find(_M.data[key],'%[%"') then
_M.data[key] = json.decode(_M.data[key])
end
end
end
end
function reprint(k,v)
if type(v) == 'table' then
for i,j in pairs(v) do
reprint(i,j)
end
else
print(k..': '..v)
end
end
function _M.data:write()
local config_file = io.open(_M.file, 'w')
for k,v in pairs(self) do
if type(v) ~= 'function' then
if type(v) == 'table' then v = json.encode(v) end
config_file:write(k..'::'..v..' #'.._M.comments[key]..'\n')
end
end
config_file:close()
end
function _M.data:print()
for k,v in pairs(self) do
if type(v) ~= 'function' then
if _M.comments[k] then print('\n'.._M.comments[k]:gsub("^%s*(.-)%s*$", "%1")..': \n') end
reprint(k,v)
end
end
end
return _M

5
config/osrm.lua Executable file
View File

@ -0,0 +1,5 @@
local config = require('config.config')
local _M = config
_M.file = 'osrm.conf' -- файл конфигурации
_M.read()
return _M.data

3
osrm.conf Normal file
View File

@ -0,0 +1,3 @@
url::http://router.project-osrm.org/ #API URL
version::v1 #API VERSION
format::json #Output format (or flatbuffers)

151
osrm/api.lua Normal file
View File

@ -0,0 +1,151 @@
local json = require('cjson')
local cURL = require("cURL")
local date = require("date")
local log = require('utils.log')
local config = require('config.osrm')
local _M = {}
_M.result = nil
_M.base = config.url
_M.format = config.format
log.outfile = 'logs/osrm_'..os.date('%Y-%m-%d')..'.log'
log.level = 'trace'
_M.profile = 'driving' -- location /route/v1/driving location /route/v1/walking location /route/v1/cycling
function defaults()
return {
['generate_hints'] = true,
['snapping'] = 'default',
['skip_waypoints'] = false
}
end
function coordinates(coords)
if coords[1] then
if type(coords[1]) == 'number' then return table.concat(coords,',') end
local out = {}
for i,c in pairs(coords) do table.insert(out,table.concat(c,',')) end
return table.concat(out,';')
end
return nil
end
function query(data)
if not data then return '' end
local str = '?'
for k,v in pairs(data) do
if str == '?' then str = str..k..'='..tostring(v)
else str = str..'&'..k..'='..tostring(v) end
end
return str
end
function get_result(str,url)
local result, err = pcall(json.decode,str)
if result then
_M.result = json.decode(str)
else
log.error(url..':'..err)
return nil, err
end
return _M.result
end
function get(service,coords,profile,options)
local str = ''
local url = ''
local res
profile = profile or _M.profile
if service == 'tile' then
url = _M.base..service..'/'..config.version..'/'..profile..'/tile('..table.concat(coords,',')..').mvt'
else
url = _M.base..service..'/'..config.version..'/'..profile..'/'..coordinates(coords)..query(options)
end
local headers = {
'Content-type: application/json',
'Accept: application/json'
}
local c = cURL.easy{
url = url,
httpheader = headers,
writefunction = function(st)
str = str..st
end
}
local ok, err = c:perform()
local code = c:getinfo_response_code()
c:close()
if not ok then return nil, err end
if code ~= 200 then return {['code']=code,['url']=url},str end
if _M.format == 'json' then
res,err = get_result(str,url)
else
res = str
end
if not res then return nil,err end
return res
end
function _M.route(coords,profile,options)
if not options then
options = defaults()
options['alternatives'] = false
options['steps'] = false
options['annotations'] = false
options['geometries'] = 'polyline'
options['overview'] = 'simplified'
options['continue_straight'] = 'default'
end
return get('route',coords,profile,options)
end
function _M.nearest(coords,profile,options)
if not options then
options = defaults()
oprions['number'] = 1
end
return get('nearest',coords,profile,options)
end
function _M.table(coords,profile,options)
if not options then
options = defaults()
options['annotations'] = 'duration'
options['fallback_coordinate'] = 'input'
end
return get('table',coords,profile,options)
end
function _M.match(coords,profile,options)
if not options then
options = defaults()
options['steps'] = false
options['geometries'] = 'polyline'
options['annotations'] = false
options['overview'] = 'simplified'
options['gaps'] = 'split'
options['tidy'] = false
end
return get('match',coords,profile,options)
end
function _M.trip(coords,profile,options)
if not options then
options = defaults()
options['roundtrip'] = true
options['source'] = 'any'
options['destination'] = 'any'
options['steps'] = false
options['annotations'] = false
options['geometries'] = 'polyline'
options['overview'] = 'simplified'
end
return get('trip',coords,profile,options)
end
function _M.tile(x,y,zoom,profile)
return get('tile',profile,{x,y,zoom})
end
return _M

16
test.lua Normal file
View File

@ -0,0 +1,16 @@
local json = require('cjson')
local api = require('osrm.api')
local date = require("date")
res,err = api.route({{48.395994,54.324645},{48.398233,54.343602}})
if not err then print(res.routes[1].distance) else print(json.encode(res)..err) end
res,err = api.route({{13.428555,52.523219},{13.428545,52.523249}},'walking')
if not err then print(json.encode(res)) else print(json.encode(res)..err) end
res,err = api.table({{50.1169,53.1912},{50.1661,53.2172},{50.0839,53.1881},{50.128,53.1985},{50.1057,53.1846}})
if not err then print(json.encode(res)) else print(json.encode(res)..err) end
res,err = api.table({{50.1169,53.1912},{50.1661,53.2172},{50.0839,53.1881},{50.128,53.1985},{50.1057,53.1846}},'driving',{['sources']=0,['destinations']=4})
if not err then print(json.encode(res)) else print(json.encode(res)..err) end

90
utils/log.lua Normal file
View File

@ -0,0 +1,90 @@
--
-- log.lua
--
-- Copyright (c) 2016 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local log = { _version = "0.1.0" }
log.usecolor = true
log.outfile = nil
log.level = "trace"
local modes = {
{ name = "trace", color = "\27[34m", },
{ name = "debug", color = "\27[36m", },
{ name = "info", color = "\27[32m", },
{ name = "warn", color = "\27[33m", },
{ name = "error", color = "\27[31m", },
{ name = "fatal", color = "\27[35m", },
}
local levels = {}
for i, v in ipairs(modes) do
levels[v.name] = i
end
local round = function(x, increment)
increment = increment or 1
x = x / increment
return (x > 0 and math.floor(x + .5) or math.ceil(x - .5)) * increment
end
local _tostring = tostring
local tostring = function(...)
local t = {}
for i = 1, select('#', ...) do
local x = select(i, ...)
if type(x) == "number" then
x = round(x, .01)
end
t[#t + 1] = _tostring(x)
end
return table.concat(t, " ")
end
for i, x in ipairs(modes) do
local nameupper = x.name:upper()
log[x.name] = function(...)
-- Return early if we're below the log level
if i < levels[log.level] then
return
end
local msg = tostring(...)
local info = debug.getinfo(2, "Sl")
local lineinfo = info.short_src .. ":" .. info.currentline
-- Output to console
print(string.format("%s[%-6s%s]%s %s: %s",
log.usecolor and x.color or "",
nameupper,
os.date("%H:%M:%S"),
log.usecolor and "\27[0m" or "",
lineinfo,
msg))
-- Output to log file
if log.outfile then
local fp = io.open(log.outfile, "a")
local str = string.format("[%-6s%s] %s: %s\n",
nameupper, os.date(), lineinfo, msg)
fp:write(str)
fp:close()
end
end
end
return log