Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- `/health/` endpoint and `HEALTHCHECK` in the Docker container.
- Docker production and development settings modules.
- `compose.prod.yml` for modifying the compose config for production.
- Added `route_type` parameter to `evaluate_route`, allowing user to specify the route type used in `route_calc`.
- Added `route_type` parameter to the route evaluation API endpoint and updated API schema.

### Changed
- Updated `evaluate_route` to call `route_calc` using keyword arguments.
- Restricted `polar-route` dependency to `>=1.1.10`.
- Docker production and development settings modules.
- `compose.prod.yml` for modifying the compose config for production.
- Re-worked docker container to create a production build-stage.
- Added static file collection to the docker entrypoint script.

Expand Down
19 changes: 18 additions & 1 deletion docs/apischema.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: PolarRoute-Server
version: 0.2.7
version: 0.2.8.dev6+gc5ccc2e7e
Comment thread
thomaszwagerman marked this conversation as resolved.
description: Backend server for serving PolarRoute and MeshiPhi assets
paths:
/api/evaluate_route:
Expand Down Expand Up @@ -785,6 +785,15 @@ components:
type: integer
nullable: true
description: 'Optional: Custom mesh ID to use for evaluation.'
route_type:
allOf:
- $ref: '#/components/schemas/RouteTypeEnum'
default: smoothed
description: |-
Type of route calculation: 'dijkstra' or 'smoothed'. Defaults to 'smoothed'.

* `dijkstra` - dijkstra
* `smoothed` - smoothed
required:
- route
RouteEvaluationResponse:
Expand All @@ -800,6 +809,14 @@ components:
required:
- evaluation_results
- polarrouteserver-version
RouteTypeEnum:
enum:
- dijkstra
- smoothed
type: string
description: |-
* `dijkstra` - dijkstra
* `smoothed` - smoothed
StatusEnum:
enum:
- PENDING
Expand Down
64 changes: 44 additions & 20 deletions polarrouteserver/route_api/utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import hashlib
import json
import logging
import os
from tempfile import NamedTemporaryFile
from typing import Union

from django.conf import settings
import haversine
import pandas as pd
from polar_route.route_calc import route_calc
from polar_route.utils import convert_decimal_days

Expand Down Expand Up @@ -171,7 +169,7 @@ def calculate_md5(filename):
return hash_md5.hexdigest()


def evaluate_route(route_json: dict, mesh: Mesh) -> dict:
def evaluate_route(route_json: dict, mesh: Mesh, route_type: str = "smoothed") -> dict:
Comment thread
thomaszwagerman marked this conversation as resolved.
"""Run calculate_route method from PolarRoute to evaluate the fuel usage and travel time of a route.

Args:
Expand All @@ -185,30 +183,56 @@ def evaluate_route(route_json: dict, mesh: Mesh) -> dict:
if route_json["features"][0].get("properties", None) is None:
route_json["features"][0]["properties"] = {"from": "Start", "to": "End"}

# route_calc only supports files, write out both route and mesh as temporary files
route_file = NamedTemporaryFile(delete=False, suffix=".json")
with open(route_file.name, "w") as fp:
json.dump(route_json, fp)
try:
# Extract all coordinates from the route (not just start/end)
coordinates = route_json["features"][0]["geometry"]["coordinates"]
properties = route_json["features"][0]["properties"]

# Preserve waypoint names from the route's properties if present,
# falling back to generic names otherwise.
from_wp = properties.get("from") or "waypoint_0"
to_wp = properties.get("to") or f"waypoint_{len(coordinates)-1}"

# Create DataFrame with all waypoints along the route. Intermediate
# waypoints are given generic names since GeoJSON LineString
# coordinates don't carry per-point names.
df_data = []
for i, coord in enumerate(coordinates):
if i == 0:
name = from_wp
elif i == len(coordinates) - 1:
name = to_wp
else:
name = f"waypoint_{i}"

df_data.append(
{
"Lat": coord[1], # lat
"Long": coord[0], # lon
"Name": name,
"order": i,
"id": 1, # All waypoints belong to the same route/track
}
)

mesh_file = NamedTemporaryFile(delete=False, suffix=".json")
with open(mesh_file.name, "w") as fp:
json.dump(mesh.json, fp)
df = pd.DataFrame(df_data)

try:
calc_route = route_calc(route_file.name, mesh_file.name)
# Use route_calc with the new API: (df, from_wp, to_wp, mesh, route_type)
calc_route = route_calc(
df=df, from_wp=from_wp, to_wp=to_wp, mesh=mesh.json, route_type=route_type
)

# Extract time and fuel information
time_days = calc_route["features"][0]["properties"]["traveltime"][-1]
time_str = convert_decimal_days(time_days)
fuel = round(calc_route["features"][0]["properties"]["fuel"][-1], 2)

except Exception as e:
logger.error(e)
logger.error(f"Error in evaluate_route: {type(e).__name__}: {e}")
import traceback

logger.error(f"Full traceback: {traceback.format_exc()}")
return None
finally:
for file in (route_file, mesh_file):
try:
os.remove(file.name)
except Exception as e:
logger.warning(f"{file} not removed due to {e}")

return dict(
route=calc_route, time_days=time_days, time_str=time_str, fuel_tonnes=fuel
Expand Down
9 changes: 8 additions & 1 deletion polarrouteserver/route_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,12 @@ class EvaluateRouteView(LoggingMixin, ResponseMixin, APIView):
allow_null=True,
help_text="Optional: Custom mesh ID to use for evaluation.",
),
"route_type": serializers.ChoiceField(
choices=["dijkstra", "smoothed"],
default="smoothed",
required=False,
help_text="Type of route calculation: 'dijkstra' or 'smoothed'. Defaults to 'smoothed'.",
),
},
),
responses={
Expand All @@ -686,6 +692,7 @@ def post(self, request):
data = request.data
route_json = data.get("route", None)
custom_mesh_id = data.get("custom_mesh_id", None)
route_type = data.get("route_type", "smoothed")
Comment thread
thomaszwagerman marked this conversation as resolved.

if custom_mesh_id:
try:
Expand All @@ -701,7 +708,7 @@ def post(self, request):

response_data = {"polarrouteserver-version": polarrouteserver_version}

result_dict = evaluate_route(route_json, meshes[0])
result_dict = evaluate_route(route_json, meshes[0], route_type=route_type)

if result_dict is None:
result_dict = {"error": "Route evaluation not possible."}
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies = [
"django-taggit",
"drf-spectacular[sidecar]",
"haversine",
"polar-route==1.0.0",
"polar-route>=1.1.10",
"psycopg>=3",
"pyyaml",
"SQLAlchemy",
Expand Down
Loading