728x90
내 맘대로 지도에 한 번 표시를 해 보았습니다.
일단 입력으로 들어간 input.json 의 경우 다음과 같은 형식이었습니다.
뭐 그냥 느낌상으로는 탈것 1번에 대해서 id를 무작위로 부여 하였다 정도로 해석을 해 봅시다.
{
"vehicles": [
{
"id": 1,
"start": [127.027621, 37.497942],
"end": [127.027621, 37.497942]
}
],
"jobs": [
{ "id": 1, "location": [127.035212, 37.495477] },
{ "id": 2, "location": [127.045126, 37.498431] },
{ "id": 3, "location": [127.027436, 37.510205] },
{ "id": 4, "location": [127.025221, 37.516245] },
{ "id": 5, "location": [127.066409, 37.499513] },
{ "id": 6, "location": [127.062032, 37.492522] },
{ "id": 7, "location": [127.046882, 37.489915] },
{ "id": 8, "location": [127.040508, 37.498161] },
{ "id": 9, "location": [127.051553, 37.514139] },
{ "id": 10, "location": [127.061707, 37.496634] },
{ "id": 11, "location": [127.034794, 37.507503] },
{ "id": 12, "location": [127.021602, 37.513866] },
{ "id": 13, "location": [127.037316, 37.495522] },
{ "id": 14, "location": [127.042979, 37.492641] },
{ "id": 15, "location": [127.057222, 37.507457] },
{ "id": 16, "location": [127.059778, 37.482721] },
{ "id": 17, "location": [127.051248, 37.476564] },
{ "id": 18, "location": [127.039653, 37.498994] },
{ "id": 19, "location": [127.034097, 37.488837] },
{ "id": 20, "location": [127.046734, 37.486172] },
{ "id": 21, "location": [127.041748, 37.509745] },
{ "id": 22, "location": [127.038188, 37.494878] },
{ "id": 23, "location": [127.048871, 37.498221] },
{ "id": 24, "location": [127.041187, 37.505308] },
{ "id": 25, "location": [127.022919, 37.517817] },
{ "id": 26, "location": [127.049905, 37.496708] },
{ "id": 27, "location": [127.053617, 37.487403] },
{ "id": 28, "location": [127.058739, 37.496972] },
{ "id": 29, "location": [127.051709, 37.508669] },
{ "id": 30, "location": [127.063031, 37.503671] },
{ "id": 31, "location": [127.045371, 37.487391] },
{ "id": 32, "location": [127.039102, 37.489985] },
{ "id": 33, "location": [127.056914, 37.509118] },
{ "id": 34, "location": [127.064212, 37.488129] },
{ "id": 35, "location": [127.047619, 37.487612] },
{ "id": 36, "location": [127.048714, 37.506603] },
{ "id": 37, "location": [127.033219, 37.497418] },
{ "id": 38, "location": [127.039881, 37.489802] },
{ "id": 39, "location": [127.034607, 37.507405] },
{ "id": 40, "location": [127.028774, 37.512234] },
{ "id": 41, "location": [127.023587, 37.516785] },
{ "id": 42, "location": [127.046448, 37.491074] },
{ "id": 43, "location": [127.049489, 37.498019] },
{ "id": 44, "location": [127.041298, 37.498793] },
{ "id": 45, "location": [127.054103, 37.507996] },
{ "id": 46, "location": [127.065716, 37.490374] },
{ "id": 47, "location": [127.034502, 37.492107] },
{ "id": 48, "location": [127.032810, 37.490583] },
{ "id": 49, "location": [127.045891, 37.488392] },
{ "id": 50, "location": [127.048809, 37.507305] }
]
}
그리고 TSP의 결과물은 다음과 같은 형식으로 나왔습니다.
{
"code":0,
"summary":
{"cost":4622,"routes":1,"unassigned":0,"setup":0,"service":0,"duration":4622,"waiting_time":0,"priority":0,"violations":[],
"computing_times":{"loading":2089,"solving":8,"routing":0}},
"unassigned":[],
"routes":
[
{"vehicle":1,"cost":4622,"setup":0,"service":0,"duration":4622,"waiting_time":0,"priority":0,
"steps":
[
{"type":"start","location":[127.123289,37.538416],"setup":0,"service":0,"waiting_time":0,"arrival":0,"duration":0,"violations":[]},
{"type":"job","location":[127.123289,37.538416],"id":1,"setup":0,"service":0,"waiting_time":0,"job":1,"arrival":0,"duration":0,"violations":[]},
{"type":"job","location":[127.1275,37.5375],"id":18,"setup":0,"service":0,"waiting_time":0,"job":18,"arrival":122,"duration":122,"violations":[]},
{"type":"job","location":[127.123789,37.539789],"id":28,"setup":0,"service":0,"waiting_time":0,"job":28,"arrival":151,"duration":151,"violations":[]},
{"type":"job","location":[127.12389,37.54089],"id":45,"setup":0,"service":0,"waiting_time":0,"job":45,"arrival":158,"duration":158,"violations":[]},
{"type":"job","location":[127.123345,37.541234],"id":33,"setup":0,"service":0,"waiting_time":0,"job":33,"arrival":188,"duration":188,"violations":[]},
{"type":"job","location":[127.1235,37.5405],"id":17,"setup":0,"service":0,"waiting_time":0,"job":17,"arrival":208,"duration":208,"violations":[]},
{"type":"job","location":[127.123678,37.540678],"id":39,"setup":0,"service":0,"waiting_time":0,"job":39,"arrival":211,"duration":211,"violations":[]},
{"type":"job","location":[127.130789,37.540789],"id":29,"setup":0,"service":0,"waiting_time":0,"job":29,"arrival":285,"duration":285,"violations":[]},
{"type":"job","location":[127.1335,37.5435],"id":20,"setup":0,"service":0,"waiting_time":0,"job":20,"arrival":350,"duration":350,"violations":[]},
{"type":"job","location":[127.135,37.545],"id":12,"setup":0,"service":0,"waiting_time":0,"job":12,"arrival":417,"duration":417,"violations":[]},
{"type":"job","location":[127.12989,37.54589],"id":46,"setup":0,"service":0,"waiting_time":0,"job":46,"arrival":489,"duration":489,"violations":[]},
{"type":"job","location":[127.129678,37.545678],"id":40,"setup":0,"service":0,"waiting_time":0,"job":40,"arrival":491,"duration":491,"violations":[]},
{"type":"job","location":[127.129234,37.546123],"id":34,"setup":0,"service":0,"waiting_time":0,"job":34,"arrival":509,"duration":509,"violations":[]},
{"type":"job","location":[127.13,37.55],"id":11,"setup":0,"service":0,"waiting_time":0,"job":11,"arrival":570,"duration":570,"violations":[]},
{"type":"job","location":[127.129153,37.551052],"id":2,"setup":0,"service":0,"waiting_time":0,"job":2,"arrival":597,"duration":597,"violations":[]},
{"type":"job","location":[127.1295,37.5505],"id":19,"setup":0,"service":0,"waiting_time":0,"job":19,"arrival":610,"duration":610,"violations":[]},
{"type":"job","location":[127.135234,37.551234],"id":35,"setup":0,"service":0,"waiting_time":0,"job":35,"arrival":701,"duration":701,"violations":[]},
{"type":"job","location":[127.135678,37.550678],"id":41,"setup":0,"service":0,"waiting_time":0,"job":41,"arrival":726,"duration":726,"violations":[]},
{"type":"job","location":[127.13589,37.55089],"id":47,"setup":0,"service":0,"waiting_time":0,"job":47,"arrival":763,"duration":763,"violations":[]},
{"type":"job","location":[127.1385,37.5485],"id":21,"setup":0,"service":0,"waiting_time":0,"job":21,"arrival":846,"duration":846,"violations":[]},
{"type":"job","location":[127.140789,37.545789],"id":30,"setup":0,"service":0,"waiting_time":0,"job":30,"arrival":899,"duration":899,"violations":[]},
{"type":"job","location":[127.150789,37.550789],"id":31,"setup":0,"service":0,"waiting_time":0,"job":31,"arrival":1007,"duration":1007,"violations":[]},
{"type":"job","location":[127.16,37.555],"id":15,"setup":0,"service":0,"waiting_time":0,"job":15,"arrival":1103,"duration":1103,"violations":[]},
{"type":"job","location":[127.160789,37.555789],"id":32,"setup":0,"service":0,"waiting_time":0,"job":32,"arrival":1109,"duration":1109,"violations":[]},
{"type":"job","location":[127.166023,37.556111],"id":8,"setup":0,"service":0,"waiting_time":0,"job":8,"arrival":1146,"duration":1146,"violations":[]},
{"type":"job","location":[127.176654,37.565231],"id":9,"setup":0,"service":0,"waiting_time":0,"job":9,"arrival":1278,"duration":1278,"violations":[]},
{"type":"job","location":[127.1685,37.5785],"id":27,"setup":0,"service":0,"waiting_time":0,"job":27,"arrival":1639,"duration":1639,"violations":[]},
{"type":"job","location":[127.1635,37.5735],"id":26,"setup":0,"service":0,"waiting_time":0,"job":26,"arrival":1817,"duration":1817,"violations":[]},
{"type":"job","location":[127.16589,37.56589],"id":50,"setup":0,"service":0,"waiting_time":0,"job":50,"arrival":2333,"duration":2333,"violations":[]},
{"type":"job","location":[127.165678,37.565678],"id":44,"setup":0,"service":0,"waiting_time":0,"job":44,"arrival":2334,"duration":2334,"violations":[]},
{"type":"job","location":[127.165234,37.566234],"id":38,"setup":0,"service":0,"waiting_time":0,"job":38,"arrival":2534,"duration":2534,"violations":[]},
{"type":"job","location":[127.17,37.565],"id":16,"setup":0,"service":0,"waiting_time":0,"job":16,"arrival":2598,"duration":2598,"violations":[]},
{"type":"job","location":[127.1585,37.5685],"id":25,"setup":0,"service":0,"waiting_time":0,"job":25,"arrival":2785,"duration":2785,"violations":[]},
{"type":"job","location":[127.156823,37.561231],"id":7,"setup":0,"service":0,"waiting_time":0,"job":7,"arrival":2901,"duration":2901,"violations":[]},
{"type":"job","location":[127.15589,37.56089],"id":49,"setup":0,"service":0,"waiting_time":0,"job":49,"arrival":2936,"duration":2936,"violations":[]},
{"type":"job","location":[127.155678,37.560678],"id":43,"setup":0,"service":0,"waiting_time":0,"job":43,"arrival":2940,"duration":2940,"violations":[]},
{"type":"job","location":[127.155234,37.561234],"id":37,"setup":0,"service":0,"waiting_time":0,"job":37,"arrival":2961,"duration":2961,"violations":[]},
{"type":"job","location":[127.1535,37.5635],"id":24,"setup":0,"service":0,"waiting_time":0,"job":24,"arrival":3005,"duration":3005,"violations":[]},
{"type":"job","location":[127.1485,37.5585],"id":23,"setup":0,"service":0,"waiting_time":0,"job":23,"arrival":3098,"duration":3098,"violations":[]},
{"type":"job","location":[127.14589,37.55589],"id":48,"setup":0,"service":0,"waiting_time":0,"job":48,"arrival":3175,"duration":3175,"violations":[]},
{"type":"job","location":[127.145678,37.555678],"id":42,"setup":0,"service":0,"waiting_time":0,"job":42,"arrival":3177,"duration":3177,"violations":[]},
{"type":"job","location":[127.145234,37.556234],"id":36,"setup":0,"service":0,"waiting_time":0,"job":36,"arrival":3184,"duration":3184,"violations":[]},
{"type":"job","location":[127.1435,37.5535],"id":22,"setup":0,"service":0,"waiting_time":0,"job":22,"arrival":3228,"duration":3228,"violations":[]},
{"type":"job","location":[127.143545,37.548152],"id":4,"setup":0,"service":0,"waiting_time":0,"job":4,"arrival":3274,"duration":3274,"violations":[]},
{"type":"job","location":[127.140395,37.538827],"id":5,"setup":0,"service":0,"waiting_time":0,"job":5,"arrival":3333,"duration":3333,"violations":[]},
{"type":"job","location":[127.14,37.535],"id":13,"setup":0,"service":0,"waiting_time":0,"job":13,"arrival":3382,"duration":3382,"violations":[]},
{"type":"job","location":[127.15,37.525],"id":14,"setup":0,"service":0,"waiting_time":0,"job":14,"arrival":3836,"duration":3836,"violations":[]},
{"type":"job","location":[127.137241,37.528131],"id":3,"setup":0,"service":0,"waiting_time":0,"job":3,"arrival":4220,"duration":4220,"violations":[]},
{"type":"job","location":[127.127874,37.537267],"id":6,"setup":0,"service":0,"waiting_time":0,"job":6,"arrival":4336,"duration":4336,"violations":[]},
{"type":"job","location":[127.1205,37.54],"id":10,"setup":0,"service":0,"waiting_time":0,"job":10,"arrival":4603,"duration":4603,"violations":[]},
{"type":"end","location":[127.123289,37.538416],"setup":0,"service":0,"waiting_time":0,"arrival":4622,"duration":4622,"violations":[]}
],
"violations":[]
}
]
}
위에서 정해 진 순번의 배열과 함께 id가 어디로 가 있는 지를 굳이 추적해 보고 싶었습니다.
우선 input.json에 대해서 순번과 ID를 표시하는 코드는 다음과 같습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Leaflet Map with Steps and IDs</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
#map {
height: 100vh;
}
.step-label {
background-color: rgba(255, 255, 255, 0.8); /* White with transparency */
border: 2px solid blue; /* Blue border */
border-radius: 5px; /* Rounded corners */
padding: 5px 10px; /* Padding for label */
font-size: 10px; /* Font size */
color: blue; /* Blue text */
font-weight: bold; /* Bold text */
}
</style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// Initialize the map
const map = L.map('map').setView([37.497942, 127.027621], 13); // Default center
// Add a tile layer to the map
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
}).addTo(map);
// Load the output.json file
fetch('output.json')
.then(response => response.json())
.then(data => {
const jobLocations = [];
let stepOrder = 0;
// Process steps in the routes array
data.routes.forEach(route => {
route.steps.forEach((step, index) => {
let labelText;
// Determine the label text for the step
if (step.type === 'start') {
labelText = `Start (0)`;
} else if (step.type === 'end') {
labelText = `End (${route.steps.length - 1})`;
} else {
labelText = `Step ${stepOrder} (ID: ${step.id})`;
stepOrder++;
}
// Create a label for each step
const label = L.divIcon({
className: 'step-label',
html: labelText, // Display the step order and ID
});
// Add a marker to the map
L.marker([step.location[1], step.location[0]], { icon: label }).addTo(map);
// Save the step location for polyline
jobLocations.push([step.location[1], step.location[0]]);
});
});
// Draw connecting lines between steps
L.polyline(jobLocations, {
color: 'blue',
weight: 3,
opacity: 0.7,
}).addTo(map);
// Adjust the map bounds to fit all locations
const bounds = L.latLngBounds(jobLocations);
map.fitBounds(bounds);
})
.catch(err => console.error('Error loading output.json:', err));
</script>
</body>
</html>
결과는 다음과 같이 나왔습니다.
좀 어지러워 보이지만 잘 찍었네요(??) ㅋ
너무 어지러워 보이니 약간 터치를 합니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Leaflet Map with Steps and IDs</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
#map {
height: 100vh;
}
.step-label {
background-color: rgba(255, 255, 255, 0.8); /* White with transparency */
border: 2px solid blue; /* Blue border */
border-radius: 5px; /* Rounded corners */
padding: 5px 10px; /* Padding for label */
font-size: 10px; /* Font size */
color: blue; /* Blue text */
font-weight: bold; /* Bold text */
}
</style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// Initialize the map
const map = L.map('map').setView([37.497942, 127.027621], 13); // Default center
// Add a tile layer to the map
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
}).addTo(map);
// Load the output.json file
fetch('output.json')
.then(response => response.json())
.then(data => {
const jobLocations = [];
let stepOrder = 0;
// Process steps in the routes array
data.routes.forEach(route => {
route.steps.forEach((step, index) => {
let labelText;
// Determine the label text for the step
if (step.type === 'start') {
// labelText = `Start (0)`;
labelText = `S(0)`;
} else if (step.type === 'end') {
//labelText = `End (${route.steps.length - 1})`;
labelText = `E(${route.steps.length - 1})`;
} else {
//labelText = `Step ${stepOrder} (ID: ${step.id})`;
labelText = `${stepOrder}(${step.id})`;
stepOrder++;
}
// Create a label for each step
const label = L.divIcon({
className: 'step-label',
html: labelText, // Display the step order and ID
});
// Add a marker to the map
L.marker([step.location[1], step.location[0]], { icon: label }).addTo(map);
// Save the step location for polyline
jobLocations.push([step.location[1], step.location[0]]);
});
});
// Draw connecting lines between steps
L.polyline(jobLocations, {
color: 'blue',
weight: 3,
opacity: 0.7,
}).addTo(map);
// Adjust the map bounds to fit all locations
const bounds = L.latLngBounds(jobLocations);
map.fitBounds(bounds);
})
.catch(err => console.error('Error loading output.json:', err));
</script>
</body>
</html>
다시 결과를 확인 해 보면,
그냥 볼만 하네요
TSP 경로 확인
그런 다음 route.json 파일을 만들기 위해서는 위의 id와 순번을 다 가지고 다녀야 합니다.
아래 코드로 TSP 경로를 다시 요청 합니다.
const fs = require('fs');
const axios = require('axios');
// OSRM 서버 URL
const OSRM_BASE_URL = 'http://localhost:5000/route/v1/driving';
// 파일 경로
const OUTPUT_JSON = './output.json';
const ROUTE_JSON = './route_with_id.json';
// 경로 요청 및 처리
async function generateRouteWithIds() {
try {
// Step 1: output.json 읽기
const outputData = JSON.parse(fs.readFileSync(OUTPUT_JSON, 'utf-8'));
if (!outputData.routes || outputData.routes.length === 0) {
throw new Error('output.json에 경로 데이터가 없습니다.');
}
// Step 2: 작업 좌표와 ID 수집
const routeSteps = outputData.routes[0].steps; // 첫 번째 경로만 처리
const coordinates = routeSteps.map((step) => step.location.join(',')).join(';'); // 경도,위도 포맷
const stepsWithId = routeSteps.map((step, index) => ({
id: step.id || "SE", // ID가 null이면 "SE"로 설정
type: step.type,
location: step.location,
order: index // 순번 추가
}));
console.log('OSRM 요청 좌표:', coordinates);
// Step 3: OSRM에 요청
const osrmUrl = `${OSRM_BASE_URL}/${coordinates}?overview=full&geometries=geojson`;
console.log('OSRM 요청 URL:', osrmUrl);
const response = await axios.get(osrmUrl);
if (response.status !== 200) {
throw new Error(`OSRM 요청 실패: ${response.statusText}`);
}
const routeData = response.data;
// Step 4: 순번 및 ID를 포함한 경로 데이터 작성
const enhancedRoute = {
geometry: routeData.routes[0].geometry,
steps: stepsWithId, // ID와 순번 포함
};
// Step 5: 결과를 route.json으로 저장
fs.writeFileSync(ROUTE_JSON, JSON.stringify(enhancedRoute, null, 2), 'utf-8');
console.log('route.json 파일 생성 완료!');
} catch (error) {
console.error('오류 발생:', error.message);
}
}
// 실행
generateRouteWithIds();
이런 느낌으로 경로들이 나오는 군요...
나온 경로를 확인 하는 코드는 다음과 같습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Leaflet Map with Steps and IDs</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
#map {
height: 100vh;
}
.step-label {
background-color: rgba(255, 255, 255, 0.8);
border: 2px solid green;
border-radius: 5px;
padding: 5px 10px;
font-size: 14px;
color: green;
font-weight: bold;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// Initialize the map
const map = L.map('map').setView([37.497942, 127.027621], 13);
// Add a tile layer to the map
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(map);
// Load the route.json file
fetch('route_with_id.json')
.then(response => response.json())
.then(data => {
const jobLocations = [];
// Draw route geometry
if (data.geometry) {
L.geoJSON(data.geometry, {
style: { color: 'blue', weight: 3, opacity: 0.7 }
}).addTo(map);
}
// Process steps
data.steps.forEach(step => {
// ID가 "SE"인 경우 처리
const labelText = `${step.type} (${step.order}) ${step.id === "SE" ? "SE" : `ID: ${step.id}`}`;
const label = L.divIcon({
className: 'step-label',
html: labelText
});
L.marker([step.location[1], step.location[0]], { icon: label }).addTo(map);
jobLocations.push([step.location[1], step.location[0]]);
});
// Fit map bounds
const bounds = L.latLngBounds(jobLocations);
map.fitBounds(bounds);
})
.catch(err => console.error('Error loading route.json:', err));
</script>
</body>
</html>
결과는 다음과 같이..
애도 어지러우니 좀 고쳐 줍니다.
근데 약간 시작점에서 2번으로 가는 것이 좀 이상한 것 같기는 하네요....
이상.
728x90
'프로그래밍 > GIS' 카테고리의 다른 글
공간 데이터베이스 구성하기 (0) | 2025.01.08 |
---|---|
PostgreSQL과 PostGIS 설치 (0) | 2025.01.07 |
[OSRM] Chat GPT와 함께하는 TSP - 마무리 (1) | 2024.12.06 |
[OSRM] Chat GPT와 함께하는 TSP - 5 시각 정보 강화 (1) | 2024.12.05 |
[OSRM] Chat GPT와 함께하는 TSP - 4 (0) | 2024.12.04 |