You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

353 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Класс для доступа к данным маршрутов
class WaypointsData {
constructor() {
// Данные для маркеров на карте
this.markersData = [];
// Регионы
this.regions = [];
// Точки
this.points = [];
}
// Заполнение данных
loadData() {
let point_counter = 0;
for (const r_idx in data['regions']) {
const region = data['regions'][r_idx]
this.regions.push({
id: r_idx,
name: region.name,
icon: 'airplane'
});
for (const p_idx in region.points) {
const point = region.points[p_idx];
const loc = point.gps.split(',');
const lat = parseFloat(loc[0].trim())
const lon = parseFloat(loc[1].trim())
this.markersData.push({
location: [lat, lon],
tooltip: {
text: point.name,
}
});
this.points.push({
id: point_counter,
location: [lat, lon],
text: point.name,
description: point.description,
regionId: r_idx,
});
point_counter++;
}
}
}
// Получить маркеры для карты
getMarkers() {
return this.markersData;
}
// Получить список регионов
getRegions() {
return this.regions;
}
// Получить точки которые относятся к региону
getRegionPoints(r_idx) {
return this.points.filter(p => p.regionId == r_idx);
}
// Получить точки по переданным индексам точек
getSelectedPoints(indexes) {
let result = [];
this.points.forEach((point) => {
if (indexes.includes(point.id)) {
result.push(point);
}
});
// Сортировка результата в порядке переданных индексов
let ordered = [];
indexes.forEach((ind)=>{
const index = result.findIndex(p=>p.id == ind);
ordered.push(result[index]);
result.slice(index, 1);
});
return ordered;
}
}
// Точки для карты
const waypoints = new WaypointsData();
const markerUrl = './img/map-marker.png';
let regionPointsWidget;
let selectedPointsWidget;
// Инициализация контрола карты
function initMap() {
const mapWidget = $('#map').dxMap({
provider: 'bing',
apiKey: {
bing: 'AnpoY0kAA4Kk5A045nQxyVbrlkNTgOuMVBitLxN_iLnZdtONf21HxUTzXwNIebES',
},
zoom: 11,
height: 440,
width: 760,
controls: true,
markerIconSrc: markerUrl,
markers: waypoints.getMarkers(),
type: 'roadmap'
}).dxMap('instance');
}
// Пересоздание списка точек указанного региона, для выбора мест к посещению
function rebuildRegionPointsList(regionId) {
if (regionPointsWidget) {
regionPointsWidget.dispose();
}
if (selectedPointsWidget) {
selectedPointsWidget.dispose();
}
regionPointsWidget = $('#regionPointsList').dxList({
dataSource: new DevExpress.data.DataSource({
store: new DevExpress.data.ArrayStore({
key: 'id',
data: waypoints.getRegionPoints(regionId),
}),
}),
height: 400,
showScrollbar: 'always',
showSelectionControls: true,
selectionMode: 'all',
selectByClick: false,
onSelectionChanged() {
rebuildSelectedPointsList(regionPointsWidget.option('selectedItemKeys'));
},
itemTemplate(data) {
const result = $('<div>').addClass('region-point');
$('<h3>').text(data.text).appendTo(result);
$('<i>').text(data.description).appendTo(result);
return result;
}
}).dxList('instance');
}
// Инициализация выпадающего списка с регионами
function initRegionDropDown() {
$('#one-section').dxDropDownButton({
text: 'Выбор региона',
icon: 'globe',
dropDownOptions: {
width: 230,
},
displayExpr: 'name',
keyExpr: 'id',
onItemClick(e) {
rebuildRegionPointsList(e.itemData.id);
DevExpress.ui.notify(`${e.itemData.name}`, 'success', 600);
},
items: waypoints.getRegions(),
});
}
// Перестроение списка выбранных точек
function rebuildSelectedPointsList(selectedPointsIndexes) {
const selected = waypoints.getSelectedPoints(selectedPointsIndexes);
if (selectedPointsWidget) {
selectedPointsWidget.dispose();
}
selectedPointsWidget = $("#selectedPointsList").dxList({
dataSource: selected,
keyExpr: 'id',
height: 400,
width: '100%',
showScrollbar: 'always',
itemDragging: {
allowReordering: true,
data: selected,
group: 'selected',
onDragStart(e) {
e.itemData = e.fromData[e.fromIndex];
},
onAdd(e) {
e.toData.splice(e.toIndex, 0, e.itemData);
e.component.reload();
},
onRemove(e) {
e.fromData.splice(e.fromIndex, 1);
e.component.reload();
},
onReorder({
fromIndex,
toIndex,
fromData,
component,
}) {
[fromData[fromIndex], fromData[toIndex]] = [fromData[toIndex], fromData[fromIndex]];
component.reload();
},
},
}).dxList('instance');
}
// Инициализация кнопки для расчета маршрута
function initRecalculateWaypointsButton() {
$('#recalculateWaypointsButton').dxButton({
stylingMode: 'contained',
text: 'Рассчитать маршрут',
type: 'default',
width: 600,
height: 40,
onClick() {
recalculateWaypoints(selectedPointsWidget.option('items'));
},
});
}
// Отображение маршрута
function showTravelPoints(indexes) {
const points = waypoints.getSelectedPoints(indexes);
let calculatedPoints = [];
let prevPoint = null;
let id = 0;
for (const idx in points) {
const point = points[idx];
if (prevPoint != null) {
const d = (calculateDistance(point.location[0], point.location[1], prevPoint.location[0], prevPoint.location[1]) / 1000.0).toFixed(2);
calculatedPoints.push({
ID: id,
Waypoint: d + ' км.'
});
id++;
}
calculatedPoints.push({
ID: id,
Waypoint: point.text
});
id++;
prevPoint = point;
}
$(() => {
$('#waypointsGrid').dxDataGrid({
dataSource: calculatedPoints,
showColumnHeaders: false,
showColumnLines: false,
showRowLines: true,
rowAlternationEnabled: true,
keyExpr: 'ID',
columns: ['Waypoint'],
showBorders: true,
});
});
}
// Расчет расстояние между двумя точками заданными координатами на поверхности Земли
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371e3; // metres
const φ1 = lat1 * Math.PI / 180; // φ, λ in radians
const φ2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c; // in metres
return d;
}
function shuffle(array) {
let currentIndex = array.length;
// While there remain elements to shuffle...
while (currentIndex != 0) {
// Pick a remaining element...
let randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]
];
}
}
// Факториал
function factorial(n) {
j = 1;
for (i = 1; i <= n; i++) {
j = j * i;
}
return j;
}
function stochasticPathFind(array) {
}
// Вычисление оптимального маршрута по начальной и конечной точке
function recalculateWaypoints(waypoints) {
if (!waypoints || waypoints.length < 1) {
DevExpress.ui.notify("Недостаточно точек для построения маршрута");
} else if (waypoints.length == 1) {
// Одна точка, просто ее выводим
showTravelPoints([waypoints[0].id]);
} else {
var start = waypoints[0];
var end = waypoints[waypoints.length - 1];
DevExpress.ui.notify("Расчет маршрута от '" + start.text + "' к '" + end.text + "'");
if (waypoints.length == 2) {
// Две точки, старт и стоп, выводим без расчета
showTravelPoints([waypoints[0].id, waypoints[1].id]);
} else if (waypoints.length == 3) {
// Три точки, старт, стоп и промежуточная выводим без расчета
showTravelPoints([waypoints[0].id, waypoints[1].id, waypoints[2].id]);
} else if (waypoints.length <= 9) {
// Если точек от 4 до 9, используем алгоритм коммивояжера
// Создаем словарь с вершинами
let vertexes = {};
for (const ind in waypoints) {
vertexes[ind] = new GraphVertex(ind);
}
// Формируем граф
const graph = new Graph();
const len = waypoints.length;
for (let i = 0; i < len - 1; i++) {
for (let j = i + 1; j < len; j++) {
const point1 = waypoints[i];
const point2 = waypoints[j];
const d = calculateDistance(point1.location[0], point1.location[1], point2.location[0], point2.location[1]);
graph.addEdge(new GraphEdge(vertexes[i], vertexes[j], d));
}
}
// Решение задачи коммивояжера
const salesmanPath = bfTravellingSalesman(graph);
let indexes = [];
for (var ind in salesmanPath) {
indexes.push(waypoints[salesmanPath[ind].value].id);
}
showTravelPoints(indexes);
} else {
// Если точек от 9, близкие к np алгоритмам расчеты слишком затратные
// применяем лучшие поиск из 200.000 лучших вариантов (9! = 362880, берем такого порядка число чтобы не грузить браузер расчетами)
}
}
}
$(() => {
waypoints.loadData();
initMap();
initRegionDropDown();
initRecalculateWaypointsButton();
});

Powered by TurnKey Linux.