add route generation route

This commit is contained in:
ilia 2023-05-25 17:12:39 +03:00
parent 5115deef66
commit b229e017a0
6 changed files with 514 additions and 65 deletions

View File

@ -0,0 +1,226 @@
city_in_hotels = ['Абзаково',
'Абрамовка',
'Абрау-Дюрсо',
'Адлер',
'Азов',
'Аксай',
'Альметьевск',
'Анапа',
'Андрианово',
'Арамиль',
'Арзамас',
'Арнеево',
'Архипо-Осиповка',
'Бабкино',
'Базы отдыха ВТО',
'Балашиха',
'Батайск',
'Беличье',
'Белореченск',
'Бердск',
'Бердяш',
'Березовка',
'Бжид',
'Битца',
'Благовещенская',
'Болтино',
'Большой Сочи',
'Бор, Нижегородская область',
'Борисово',
'Борносово',
'Будённовск',
'Вардане',
'Васильево, Ленинградская область',
'Васкелово',
'Вербилки',
'Веселовка, Краснодарский край',
'Видное',
'Витязево',
'Владимировка',
'Внуково',
'Воскресенск',
'Вотря',
'Всеволожск',
'Всходы',
'Выборг',
'Вырубово',
'Гатчина',
'Гвардейское',
'Геленджик',
'Голиково',
'Головинка Краснодарский край',
'Голубицкая',
'Горки',
'Городец',
'Горячий ключ',
'Григорчиково',
'Гуамка',
'Д/О Авангард',
'Дагомыс',
'Джемете',
'Джубга',
'Дзержинск',
'Дивеево',
'Дивногорье, Краснодарский край',
'Дивноморское',
'Дмитров',
'Домодедово',
'Дракино',
'Дранишники',
'Дубечино',
'Егорьевск',
'Ейск',
'Екатеринбург',
'Ершово',
'Ессентуки',
'Железноводск',
'Жуковский',
'За Родину',
'Звенигород',
'Зеленая поляна',
'Ивантеевка',
'Ильичево',
'Иннолово ',
'Иноземцево',
'Исаково',
'Истра',
'Кабардинка',
'Казань',
'Каменск-Шахтинский',
'Каневская',
'Кингисепп',
'Кисловодск',
'Клин',
'Коломна',
'Коробицыно',
'Королев',
'Косулино',
'Котельники',
'Красная Горка',
'Красная Поляна',
'Красногорск',
'Краснодар',
'Красный Колос',
'Кудряшовский',
'Курово',
'Кусимовского Рудника',
'Кучугуры',
'Лабинск',
'Лазаревское',
'Лермонтово',
'Лесной городок',
'Лодейное Поле',
'Лоо',
'Лосево',
'Люберцы',
'Магнитогорск',
'Малые Решники',
'Марьино, Ленинградская область',
'Маяковского',
'Мезмай',
'Мещерино',
'Миасс',
'Минеральные Воды',
'Мистолово',
'Мишуткино',
'Можайск',
'Москва',
'Мостовской',
'Мытищи',
'Набережные Челны',
'Наро-Фоминск',
'Нарынка',
'Небуг',
'Нестерово',
'Нижний Новгород',
'Нижний Тагил',
'Новая',
'Новоабзаково',
'Нововолково',
'Новомихайловский',
'Новороссийск',
'Новосибирск',
'Новочеркасск',
'Новый путь',
'Ногинск',
'Нурлат',
'Овсяники',
'Одинцово',
'Озеры',
'Оксино',
'Октябрьский, Московская область',
'Ольгинка',
'Остров, Московская область',
'Павловск',
'Падиково',
'Пересвет',
'Платформа 69-й километр, Сосновское сельское поселение',
'Подольск',
'Подпорожье',
'Полтавская',
'Приморско-Ахтарск',
'Приозерск',
'Прохорово',
'Пушкино',
'Пятигорск',
'Раменское',
'Реутов',
'Рождествено',
'Роза Хутор',
'Ростов-на-Дону',
'Рощино',
'Руза',
'Санкт-Петербург',
'Светлое',
'Светлый',
'Свирица',
'Сергиев Посад',
'Серпухов',
'Симагино',
'Сириус',
'Скоково',
'Снегири',
'Солнечногорск',
'Солохаул',
'Сосново',
'Сосновый Бор, Ленинградская область',
'Сосновый Бор, Московская область',
'Софрино',
'Сочи',
'Ставрополь',
'Станица Динская',
'Станица Должанская',
'Старая Руза',
'Степаньково',
'Суйда',
'Сукко',
'Супсех',
'Таганрог',
'Тарасово',
'Тимашевск',
'Тихвин',
'Тихорецк',
'Тобольск',
'Туапсе',
'Тучково',
'Тюмень',
'Увильды ',
'Углегорский',
'Удельная',
'Усть-Койсуг',
'Усть-Лабинск',
'Уфа',
'Ушаки',
'Фрязино',
'Хадыженск',
'Химки',
'Хоста',
'Чебаркуль',
'Челябинск',
'Чехов, Сахалинская область',
'Чудская',
'Шахты',
'Широкая балка',
'Щёлково',
'Эсто-Садок',
'Якорная щель']

View File

@ -1,7 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from passfinder.events.models import Hotel, HotelPhone, City, Event, BasePoint, Region from passfinder.events.models import Hotel, HotelPhone, City, Event, BasePoint, Region, Restaurant
class HotelPhoneSerializer(serializers.ModelSerializer): class HotelPhoneSerializer(serializers.ModelSerializer):
@ -49,7 +49,7 @@ class RouteSerializer(serializers.Serializer):
class RouteInputSerializer(serializers.Serializer): class RouteInputSerializer(serializers.Serializer):
date_from = serializers.DateField(required=False, allow_null=True) date_from = serializers.DateField(required=False, allow_null=True)
date_to = serializers.DateField(required=False, allow_null=True) date_to = serializers.DateField(required=False, allow_null=True)
region = serializers.CharField( city = serializers.CharField(
min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True min_length=24, max_length=24, required=False, allow_blank=True, allow_null=True
) )
@ -106,3 +106,16 @@ def validate(self, data):
class InputRouteSerializer(serializers.Serializer): class InputRouteSerializer(serializers.Serializer):
points = serializers.ListSerializer(child=InputRoutePointSerializer()) points = serializers.ListSerializer(child=InputRoutePointSerializer())
class ResaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
exclude = ('phones', )
class ObjectRouteSerializer(serializers.Serializer):
lat = serializers.FloatField()
lon = serializers.FloatField()
title = serializers.CharField()
description = serializers.CharField()

View File

@ -1,6 +1,12 @@
from rest_framework.generics import GenericAPIView, ListAPIView, get_object_or_404 from rest_framework.generics import GenericAPIView, ListAPIView, get_object_or_404
from rest_framework.response import Response from rest_framework.response import Response
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
from django.db.models import Count
from random import choice
from passfinder.recomendations.service.service import generate_tour
from datetime import timedelta, datetime
from .consts import *
from passfinder.events.api.serializers import ( from passfinder.events.api.serializers import (
PointSerializer, PointSerializer,
@ -46,34 +52,34 @@ def post(self, request):
serializer = RouteInputSerializer(data=request.data) serializer = RouteInputSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
data = serializer.data data = serializer.data
region = data["region"] city_id = data["city"]
routes = [] start_date = datetime.strptime(data['date_from'], '%Y-%m-%d')
if region: end_date = datetime.strptime(data['date_to'], '%Y-%m-%d')
region = get_object_or_404(Region, oid=region) region = None
for _ in range(2):
routes.append( if city_id:
{ region = get_object_or_404(City, oid=city_id)
"name": "bebra",
"date": data["date_from"],
"description": "bebra bebra bebra",
"points": PointSerializer(many=True).to_representation(
region.points.all().order_by("?")[:10]
),
}
)
else: else:
for _ in range(10): region = choice(City.objects.annotate(points_count=Count('points')).filter(title__in=city_in_hotels))
routes.append(
{ if not start_date and end_date:
"name": "bebra", tour_length = choice([timedelta(days=i) for i in range(1, 4)])
"date": data["date_from"], start_date = end_date - tour_length
"description": "bebra bebra bebra", if not end_date and start_date:
"points": PointSerializer(many=True).to_representation( tour_length = choice([timedelta(days=i) for i in range(1, 4)])
BasePoint.objects.order_by("?")[:10] end_date = end_date + tour_length
), if not end_date and not start_date:
} max_date = datetime.now() + timedelta(days=15)
) start_date = choice([max_date - timedelta(days=i) for i in range(1, 5)])
return Response(data=routes) tour_length = choice([timedelta(days=i) for i in range(1, 4)])
end_date = start_date + tour_length
print(request.user, region, start_date, end_date)
tour = generate_tour(request.user, region, start_date, end_date)
print(len(tour[1]))
return Response(data=tour[0])
class ListRegionApiView(ListAPIView): class ListRegionApiView(ListAPIView):

View File

@ -0,0 +1,57 @@
# Generated by Django 4.2.1 on 2023-05-24 15:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("events", "0019_category_pointcategory_delete_tag"),
("recomendations", "0005_userpreferences_prefferred_attractions_and_more"),
]
operations = [
migrations.CreateModel(
name="NearestRestaurantToHotel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"hotel",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="events.hotel"
),
),
("restaurants", models.ManyToManyField(to="events.restaurant")),
],
),
migrations.CreateModel(
name="NearestRestaurantToEvent",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"event",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="events.event"
),
),
("restaurants", models.ManyToManyField(to="events.restaurant")),
],
),
]

View File

@ -1,6 +1,6 @@
from django.db import models from django.db import models
from passfinder.users.models import User from passfinder.users.models import User
from passfinder.events.models import Event, Hotel from passfinder.events.models import Event, Hotel, Restaurant
class UserPreferences(models.Model): class UserPreferences(models.Model):
@ -27,7 +27,36 @@ class NearestEvent(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='nearest_model_rel') event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='nearest_model_rel')
nearest = models.ManyToManyField(Event, related_name='nearest_model_rev_rel') nearest = models.ManyToManyField(Event, related_name='nearest_model_rev_rel')
"""
from passfinder.recomendations.service.service import generate_tour
from passfinder.users.models import User
from passfinder.events.models import City
from datetime import datetime
start_date = datetime(year=2023, month=6, day=10)
end_date = datetime(year=2023, month=6, day=13)
c = City.objects.get(title='Таганрог')
u = User.objects.all()[0]
generate_tour(u, c, start_date, end_date)
"""
class NearestHotel(models.Model): class NearestHotel(models.Model):
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name='nearest_hotel_rel') hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE, related_name='nearest_hotel_rel')
nearest_events = models.ManyToManyField(Event, related_name='nearest_hotel_rev_rel') nearest_events = models.ManyToManyField(Event, related_name='nearest_hotel_rev_rel')
class NearestRestaurantToEvent(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
restaurants = models.ManyToManyField(Restaurant)
class NearestRestaurantToHotel(models.Model):
hotel = models.ForeignKey(Hotel, on_delete=models.CASCADE)
restaurants = models.ManyToManyField(Restaurant)

View File

@ -1,8 +1,9 @@
from annoy import AnnoyIndex from annoy import AnnoyIndex
from .mapping.mapping import * from .mapping.mapping import *
from .models.models import * from .models.models import *
from passfinder.events.models import Event, Region, Hotel, BasePoint, City from passfinder.events.models import Event, Region, Hotel, BasePoint, City, Restaurant
from passfinder.recomendations.models import UserPreferences, NearestEvent, NearestHotel from passfinder.events.api.serializers import HotelSerializer, EventSerializer, ResaurantSerializer, ObjectRouteSerializer
from passfinder.recomendations.models import *
from random import choice, sample from random import choice, sample
from collections import Counter from collections import Counter
from passfinder.users.models import User from passfinder.users.models import User
@ -10,6 +11,7 @@
from django.db.models import Q from django.db.models import Q
from geopy.distance import geodesic as GD from geopy.distance import geodesic as GD
from datetime import timedelta, time, datetime from datetime import timedelta, time, datetime
from gevent.pool import Pool
def get_nearest_(instance_model, model_type, mapping, rev_mapping, nearest_n, ml_model): def get_nearest_(instance_model, model_type, mapping, rev_mapping, nearest_n, ml_model):
@ -250,14 +252,14 @@ def get_personal_movies_recommendation(user):
def dist_func(event1: Event, event2: Event): def dist_func(event1: Event, event2: Event):
# cords1 = [event1.lat, event1.lon] cords1 = [event1.lat, event1.lon]
# cords2 = [event2.lat, event2.lon] cords2 = [event2.lat, event2.lon]
# try: try:
# dist = GD(cords1, cords2).km dist = GD(cords1, cords2).km
# return dist return dist
# except: except:
# return 1000000 return 1000000
return (event1.lon - event2.lon) ** 2 + (event1.lat - event2.lat) ** 2 #return (event1.lon - event2.lon) ** 2 + (event1.lat - event2.lat) ** 2
def generate_nearest(): def generate_nearest():
@ -288,6 +290,26 @@ def generate_hotel_nearest():
print(i) print(i)
def generate_nearest_restaurants():
rests = list(Restaurant.objects.all())
for i, event in enumerate(Event.objects.all()):
sorted_rests = list(sorted(rests.copy(), key=lambda x: dist_func(x, event)))
nr = NearestRestaurantToEvent.objects.create(event=event)
nr.restaurants.set(sorted_rests[0:20])
nr.save()
if i % 100 == 0:
print(i)
for i, hotel in enumerate(Hotel.objects.all()):
sorted_rests = list(sorted(rests.copy(), key=lambda x: dist_func(x, hotel)))
nr = NearestRestaurantToHotel.objects.create(hotel=hotel)
nr.restaurants.set(sorted_rests[0:20])
nr.save()
if i % 100 == 0:
print(i)
def match_points(): def match_points():
regions = list(City.objects.all()) regions = list(City.objects.all())
for i, point in enumerate(Event.objects.all()): for i, point in enumerate(Event.objects.all()):
@ -339,14 +361,18 @@ def calculate_favorite_metric(event: Event, user: User):
def get_nearest_favorite( def get_nearest_favorite(
events: Iterable[Event], user: User, exclude_events: Iterable[Event] = [] events: Iterable[Event], user: User, exclude_events: Iterable[Event] = []
): ):
print(events)
first_event = None first_event = None
for candidate in events: for candidate in events:
if candidate not in exclude_events: if candidate not in exclude_events:
first_event = candidate first_event = candidate
break break
if first_event is None:
result = events[0]
else:
result = first_event result = first_event
result_min = calculate_favorite_metric(result, user) result_min = 1000000
for event in events: for event in events:
if event in exclude_events: if event in exclude_events:
continue continue
@ -372,50 +398,142 @@ def generate_route(point1: BasePoint, point2: BasePoint):
time = time_func(distance) time = time_func(distance)
return { return {
"type": "transition", "type": "transition",
"from": point1,
"to": point2,
"distance": distance, "distance": distance,
"time": time, "time": time.seconds,
} }
def generate_point(point: BasePoint): def generate_point(point: BasePoint):
event_data = ObjectRouteSerializer(point).data
return { return {
"type": "point", "type": "point",
"point": point, "point": event_data,
"point_type": "", "point_type": "point",
"time": timedelta(minutes=90+choice(range(-10, 90, 10))) "time": timedelta(minutes=90+choice(range(-10, 90, 10))).seconds
} }
def generate_path(region: Region, user: User):
def generate_restaurant(point: BasePoint):
rest_data = ObjectRouteSerializer(point).data
return {
"type": "point",
"point": rest_data,
"point_type": "restaurant",
"time": timedelta(minutes=90+choice(range(-10, 90, 10))).seconds
}
def generate_multiple_tours(user: User, city: City, start_date: datetime.date, end_date: datetime.date):
hotels = sample(list(Hotel.objects.filter(city=city)), 5)
pool = Pool(5)
return pool.map(generate_tour, [(user, start_date, end_date, hotel) for hotel in hotels])
def generate_tour(user: User, city: City, start_date: datetime.date, end_date: datetime.date):
print("start hotel")
hotel = choice(list(Hotel.objects.filter(city=city)))
print("end hotel")
current_date = start_date
paths, points = [], []
while current_date < end_date:
print("start day gen")
local_points, local_paths = generate_path(user, points, hotel)
points.extend(local_points)
paths.append(
{
'date': current_date,
'paths': local_paths
}
)
print("end day gen")
current_date += timedelta(days=1)
return paths, points
def generate_hotel(hotel: Hotel):
hotel_data = ObjectRouteSerializer(hotel).data
return {
"type": "point",
"point": hotel_data,
"point_type": "hotel",
"time": timedelta(minutes=90+choice(range(-10, 90, 10))).seconds
}
def generate_path(user: User, disallowed_points: Iterable[BasePoint], hotel: Hotel):
# region_events = Event.objects.filter(region=region) # region_events = Event.objects.filter(region=region)
hotel = filter_hotel(region, user, []) #candidates = NearestHotel.objects.get(hotel=hotel).nearest_events.all()
allowed_types = ['museum', 'attraction']
candidates = NearestHotel.objects.get(hotel=hotel).nearest_events.all() print("start start point gen")
start_point = NearestRestaurantToHotel.objects.get(hotel=hotel).restaurants.first()
start_point = get_nearest_favorite(candidates, user, []) print("end start point gen")
candidates = NearestEvent.objects.get(event=start_point).nearest.all()
print("start first cand gen")
candidates = list(filter(lambda x: x.type in allowed_types, map(lambda x: x.event, start_point.nearestrestauranttoevent_set.all()[0:100])))
print("end first cand gen")
points = [start_point] points = [start_point]
path = [
path = [generate_point(points[-1])] generate_hotel(hotel),
generate_route(start_point, hotel),
generate_restaurant(points[-1])
]
start_time = datetime.combine(datetime.now(), time(hour=10)) start_time = datetime.combine(datetime.now(), time(hour=10))
while start_time.hour < 22: how_many_eat = 1
candidates = NearestEvent.objects.get(event=points[-1]).nearest.all()
points.append(get_nearest_favorite(candidates, user, points))
while start_time.hour < 22:
print(start_time)
if (start_time.hour > 13 and how_many_eat == 1) or (start_time.hour > 20 and how_many_eat == 2):
print("start rest event gen")
point = NearestRestaurantToEvent.objects.get(event=points[-1]).restaurants.all()[0]
points.append(point)
candidates = list(filter(lambda x: x.type in allowed_types, map(lambda x: x.event, point.nearestrestauranttoevent_set.all()[0:100])))
if not len(candidates):
candidates = list(map(lambda x: x.event, point.nearestrestauranttoevent_set.all()[0:100]))
path.append(generate_restaurant(points[-1]))
start_time += timedelta(seconds=path[-1]['time'])
how_many_eat += 1
print("start rest event gen")
continue
if start_time.hour > 17:
allowed_types = ['play', 'concert', 'movie']
print("start events gen")
if candidates is None:
candidates = NearestEvent.objects.get(event=points[-1]).nearest.filter(type__in=allowed_types)
if not len(candidates):
candidates = NearestEvent.objects.get(event=points[-1]).nearest.all()
print("end events gen")
print("start events select")
try:
print(points)
points.append(get_nearest_favorite(candidates, user, points + disallowed_points))
except AttributeError:
points.append(get_nearest_favorite(candidates, user, points))
print("end events select")
print("start route gen")
transition_route = generate_route(points[-1], points[-2]) transition_route = generate_route(points[-1], points[-2])
start_time += transition_route["time"] start_time += timedelta(seconds=transition_route["time"])
point_route = generate_point(points[-1]) point_route = generate_point(points[-1])
start_time += point_route["time"] start_time += timedelta(seconds=point_route["time"])
path.extend([transition_route, point_route]) path.extend([transition_route, point_route])
candidates = None
return hotel, points, path print("end route gen")
return points, path
def calculate_distance(sample1: Event, samples: Iterable[Event], model: AnnoyIndex, rev_mapping): def calculate_distance(sample1: Event, samples: Iterable[Event], model: AnnoyIndex, rev_mapping):