2017-11-20 02:28:08 +01:00
|
|
|
|
import operator
|
|
|
|
|
import pickle
|
|
|
|
|
from collections import deque
|
2023-12-07 19:13:42 +01:00
|
|
|
|
from dataclasses import dataclass, field
|
2017-11-20 21:10:03 +01:00
|
|
|
|
from itertools import chain
|
2023-12-07 19:13:42 +01:00
|
|
|
|
from typing import Optional
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from scipy.interpolate import NearestNDInterpolator
|
2023-12-11 18:48:40 +01:00
|
|
|
|
from shapely import Geometry, MultiPolygon, prepared
|
2017-11-20 02:28:08 +01:00
|
|
|
|
from shapely.geometry import GeometryCollection
|
|
|
|
|
from shapely.ops import unary_union
|
2023-12-07 19:13:42 +01:00
|
|
|
|
from shapely.prepared import PreparedGeometry
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2017-11-20 21:10:03 +01:00
|
|
|
|
from c3nav.mapdata.models import Level, MapUpdate, Source
|
2017-11-20 02:38:35 +01:00
|
|
|
|
from c3nav.mapdata.render.geometry import AltitudeAreaGeometries, LevelGeometries
|
2017-11-20 17:21:19 +01:00
|
|
|
|
from c3nav.mapdata.utils.cache import AccessRestrictionAffected, MapHistory
|
2017-11-20 21:10:03 +01:00
|
|
|
|
from c3nav.mapdata.utils.cache.package import CachePackage
|
2023-07-24 11:51:25 +02:00
|
|
|
|
from c3nav.mapdata.utils.geometry import get_rings, unwrap_geom
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2023-12-07 17:28:13 +01:00
|
|
|
|
try:
|
|
|
|
|
from asgiref.local import Local as LocalContext
|
|
|
|
|
except ImportError:
|
|
|
|
|
from threading import local as LocalContext
|
|
|
|
|
|
2017-11-20 02:28:08 +01:00
|
|
|
|
empty_geometry_collection = GeometryCollection()
|
|
|
|
|
|
|
|
|
|
|
2023-12-07 19:13:42 +01:00
|
|
|
|
@dataclass
|
2017-11-20 02:28:08 +01:00
|
|
|
|
class Cropper:
|
2023-12-07 19:13:42 +01:00
|
|
|
|
geometry: Optional[Geometry]
|
|
|
|
|
geometry_prep: Optional[PreparedGeometry] = field(init=False, repr=False)
|
|
|
|
|
|
|
|
|
|
def __post_init__(self):
|
|
|
|
|
self.geometry_prep = None if self.geometry is None else prepared.prep(unwrap_geom(self.geometry))
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
def intersection(self, other):
|
|
|
|
|
if self.geometry is None:
|
|
|
|
|
return other
|
|
|
|
|
if self.geometry_prep.intersects(other):
|
|
|
|
|
return self.geometry.intersection(other)
|
|
|
|
|
return empty_geometry_collection
|
|
|
|
|
|
|
|
|
|
|
2023-12-07 19:13:42 +01:00
|
|
|
|
@dataclass
|
2017-11-20 02:28:08 +01:00
|
|
|
|
class LevelRenderData:
|
2017-12-15 00:02:40 +01:00
|
|
|
|
"""
|
|
|
|
|
Renderdata for a level to display.
|
|
|
|
|
This contains multiple LevelGeometries instances because you might to look through holes onto lower levels.
|
|
|
|
|
"""
|
2023-12-07 19:13:42 +01:00
|
|
|
|
base_altitude: float
|
|
|
|
|
lowest_important_level: int
|
|
|
|
|
levels: list[LevelGeometries] = field(default_factory=list)
|
|
|
|
|
darken_area: MultiPolygon | None = None
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def rebuild():
|
2023-12-07 19:13:42 +01:00
|
|
|
|
# Levels are automatically sorted by base_altitude, ascending
|
2017-11-20 02:28:08 +01:00
|
|
|
|
levels = tuple(Level.objects.prefetch_related('altitudeareas', 'buildings', 'doors', 'spaces',
|
|
|
|
|
'spaces__holes', 'spaces__areas', 'spaces__columns',
|
|
|
|
|
'spaces__obstacles', 'spaces__lineobstacles',
|
|
|
|
|
'spaces__groups', 'spaces__ramps'))
|
|
|
|
|
|
2017-11-20 21:10:03 +01:00
|
|
|
|
package = CachePackage(bounds=tuple(chain(*Source.max_bounds())))
|
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
# todo: we should check that levels on top come before their levels as they should
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
first pass in reverse to collect some data that we need later
|
|
|
|
|
"""
|
|
|
|
|
# level geometry for every single level
|
2023-12-07 19:13:42 +01:00
|
|
|
|
single_level_geoms: dict[int, LevelGeometries] = {}
|
2023-12-07 20:11:55 +01:00
|
|
|
|
# interpolator are used to create the 3d mesh
|
2017-11-20 02:28:08 +01:00
|
|
|
|
interpolators = {}
|
2023-12-07 19:13:42 +01:00
|
|
|
|
last_interpolator: NearestNDInterpolator | None = None
|
2023-12-07 20:11:55 +01:00
|
|
|
|
# altitudeareas of levels on top are are collected on the way down to supply to the levelgeometries builder
|
2023-12-07 19:13:42 +01:00
|
|
|
|
altitudeareas_above = [] # todo: typing
|
2023-12-07 20:11:55 +01:00
|
|
|
|
for render_level in reversed(levels):
|
2023-12-07 19:13:42 +01:00
|
|
|
|
# build level geometry for every single level
|
2023-12-07 20:11:55 +01:00
|
|
|
|
single_level_geoms[render_level.pk] = LevelGeometries.build_for_level(render_level, altitudeareas_above)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2017-12-15 00:02:40 +01:00
|
|
|
|
# ignore intermediate levels in this pass
|
2023-12-07 20:11:55 +01:00
|
|
|
|
if render_level.on_top_of_id is not None:
|
|
|
|
|
# todo: shouldn't this be cleared or something?
|
|
|
|
|
altitudeareas_above.extend(single_level_geoms[render_level.pk].altitudeareas)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
altitudeareas_above.sort(key=operator.attrgetter('altitude'))
|
|
|
|
|
continue
|
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
# create interpolator to create the pieces that fit multiple 3d layers together
|
2017-11-20 02:28:08 +01:00
|
|
|
|
if last_interpolator is not None:
|
2023-12-07 20:11:55 +01:00
|
|
|
|
interpolators[render_level.pk] = last_interpolator
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
coords = deque()
|
|
|
|
|
values = deque()
|
2023-12-07 20:11:55 +01:00
|
|
|
|
for area in single_level_geoms[render_level.pk].altitudeareas:
|
2017-11-20 02:28:08 +01:00
|
|
|
|
new_coords = np.vstack(tuple(np.array(ring.coords) for ring in get_rings(area.geometry)))
|
|
|
|
|
coords.append(new_coords)
|
|
|
|
|
values.append(np.full((new_coords.shape[0], 1), fill_value=area.altitude))
|
|
|
|
|
|
2018-01-05 20:52:35 +01:00
|
|
|
|
if coords:
|
|
|
|
|
last_interpolator = NearestNDInterpolator(np.vstack(coords), np.vstack(values))
|
|
|
|
|
else:
|
2023-12-11 19:02:19 +01:00
|
|
|
|
last_interpolator = NearestNDInterpolator(np.array([[0, 0]]),
|
|
|
|
|
np.array([float(render_level.base_altitude)]))
|
2023-12-07 20:11:55 +01:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
second pass, forward to create the LevelRenderData for each level
|
|
|
|
|
"""
|
|
|
|
|
for render_level in levels:
|
|
|
|
|
# we don't create render data for on_top_of levels
|
|
|
|
|
if render_level.on_top_of_id is not None:
|
2017-11-20 02:28:08 +01:00
|
|
|
|
continue
|
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
map_history = MapHistory.open_level(render_level.pk, 'base')
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
# collect potentially relevant levels for rendering this level
|
|
|
|
|
# these are all levels that are on_top_of this level or below this level
|
|
|
|
|
relevant_levels = tuple(
|
|
|
|
|
sublevel for sublevel in levels
|
|
|
|
|
if sublevel.on_top_of_id == render_level.pk or sublevel.base_altitude <= render_level.base_altitude
|
|
|
|
|
)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
"""
|
|
|
|
|
choose a crop area for each level. non-intermediate levels (not on_top_of) below the one that we are
|
|
|
|
|
currently rendering will be cropped to only render content that is visible through holes indoors in the
|
|
|
|
|
levels above them.
|
|
|
|
|
"""
|
|
|
|
|
# area to crop each level to, by id
|
|
|
|
|
level_crop_to: dict[int, Cropper] = {}
|
|
|
|
|
# current remaining area that we're cropping to – None means no cropping
|
2017-11-20 02:28:08 +01:00
|
|
|
|
crop_to = None
|
|
|
|
|
primary_level_count = 0
|
2017-12-20 12:21:44 +01:00
|
|
|
|
main_level_passed = 0
|
|
|
|
|
lowest_important_level = None
|
2018-12-06 17:37:54 +01:00
|
|
|
|
last_lower_bound = None
|
2023-12-07 20:11:55 +01:00
|
|
|
|
for level in reversed(relevant_levels): # reversed means we are going down
|
|
|
|
|
geoms = single_level_geoms[level.pk]
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
if geoms.holes is not None:
|
|
|
|
|
primary_level_count += 1
|
|
|
|
|
|
2017-12-20 12:21:44 +01:00
|
|
|
|
# get lowest intermediate level directly below main level
|
|
|
|
|
if not main_level_passed:
|
2023-12-07 20:11:55 +01:00
|
|
|
|
if geoms.pk == render_level.pk:
|
2017-12-20 12:21:44 +01:00
|
|
|
|
main_level_passed = 1
|
|
|
|
|
else:
|
2023-12-07 20:11:55 +01:00
|
|
|
|
if not level.on_top_of_id:
|
2017-12-20 12:21:44 +01:00
|
|
|
|
main_level_passed += 1
|
|
|
|
|
if main_level_passed < 2:
|
2023-12-07 20:11:55 +01:00
|
|
|
|
lowest_important_level = level
|
2017-12-20 12:21:44 +01:00
|
|
|
|
|
2018-12-06 17:37:54 +01:00
|
|
|
|
# make upper bounds
|
|
|
|
|
if geoms.on_top_of_id is None:
|
|
|
|
|
if last_lower_bound is None:
|
|
|
|
|
geoms.upper_bound = geoms.max_altitude+geoms.max_height
|
|
|
|
|
else:
|
|
|
|
|
geoms.upper_bound = last_lower_bound
|
|
|
|
|
last_lower_bound = geoms.lower_bound
|
|
|
|
|
|
2017-11-20 02:28:08 +01:00
|
|
|
|
# set crop area if we area on the second primary layer from top or below
|
2023-12-07 20:11:55 +01:00
|
|
|
|
level_crop_to[level.pk] = Cropper(crop_to if primary_level_count > 1 else None)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
if geoms.holes is not None: # there area holes on this area
|
2017-11-20 02:28:08 +01:00
|
|
|
|
if crop_to is None:
|
|
|
|
|
crop_to = geoms.holes
|
|
|
|
|
else:
|
|
|
|
|
crop_to = crop_to.intersection(geoms.holes)
|
|
|
|
|
|
|
|
|
|
if crop_to.is_empty:
|
|
|
|
|
break
|
|
|
|
|
|
2023-12-07 19:13:42 +01:00
|
|
|
|
render_data = LevelRenderData(
|
2023-12-07 20:11:55 +01:00
|
|
|
|
base_altitude=render_level.base_altitude,
|
2023-12-07 19:13:42 +01:00
|
|
|
|
lowest_important_level=lowest_important_level.pk,
|
|
|
|
|
)
|
2017-11-20 17:21:19 +01:00
|
|
|
|
access_restriction_affected = {}
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2017-12-15 00:02:40 +01:00
|
|
|
|
# go through sublevels, get their level geometries and crop them
|
2017-12-20 12:21:44 +01:00
|
|
|
|
lowest_important_level_passed = False
|
2023-12-07 20:11:55 +01:00
|
|
|
|
for level in relevant_levels:
|
2017-11-20 02:28:08 +01:00
|
|
|
|
try:
|
2023-12-07 20:11:55 +01:00
|
|
|
|
crop_to = level_crop_to[level.pk]
|
2017-11-20 02:28:08 +01:00
|
|
|
|
except KeyError:
|
2023-12-07 20:12:10 +01:00
|
|
|
|
continue
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
old_geoms = single_level_geoms[level.pk]
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
if render_data.lowest_important_level == level.pk:
|
2017-12-20 12:21:44 +01:00
|
|
|
|
lowest_important_level_passed = True
|
|
|
|
|
|
|
|
|
|
if old_geoms.holes and render_data.darken_area is None and lowest_important_level_passed:
|
|
|
|
|
render_data.darken_area = old_geoms.holes
|
|
|
|
|
|
2017-11-20 02:28:08 +01:00
|
|
|
|
if crop_to.geometry is not None:
|
2023-12-07 20:11:55 +01:00
|
|
|
|
map_history.composite(MapHistory.open_level(level.pk, 'base'), crop_to.geometry)
|
|
|
|
|
elif render_level.pk != level.pk:
|
|
|
|
|
map_history.composite(MapHistory.open_level(level.pk, 'base'), None)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
new_geoms = LevelGeometries()
|
2018-12-05 19:22:20 +01:00
|
|
|
|
new_geoms.buildings = crop_to.intersection(old_geoms.buildings)
|
2018-12-05 23:15:28 +01:00
|
|
|
|
if old_geoms.on_top_of_id is None:
|
|
|
|
|
new_geoms.holes = crop_to.intersection(old_geoms.holes)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
new_geoms.doors = crop_to.intersection(old_geoms.doors)
|
|
|
|
|
new_geoms.walls = crop_to.intersection(old_geoms.walls)
|
|
|
|
|
new_geoms.all_walls = crop_to.intersection(old_geoms.all_walls)
|
|
|
|
|
new_geoms.short_walls = tuple((altitude, geom) for altitude, geom in tuple(
|
|
|
|
|
(altitude, crop_to.intersection(geom))
|
|
|
|
|
for altitude, geom in old_geoms.short_walls
|
|
|
|
|
) if not geom.is_empty)
|
|
|
|
|
|
|
|
|
|
for altitudearea in old_geoms.altitudeareas:
|
2023-07-24 11:51:25 +02:00
|
|
|
|
new_geometry = crop_to.intersection(unwrap_geom(altitudearea.geometry))
|
2017-11-20 02:28:08 +01:00
|
|
|
|
if new_geometry.is_empty:
|
|
|
|
|
continue
|
|
|
|
|
new_geometry_prep = prepared.prep(new_geometry)
|
|
|
|
|
|
|
|
|
|
new_altitudearea = AltitudeAreaGeometries()
|
|
|
|
|
new_altitudearea.geometry = new_geometry
|
|
|
|
|
new_altitudearea.altitude = altitudearea.altitude
|
|
|
|
|
new_altitudearea.altitude2 = altitudearea.altitude2
|
|
|
|
|
new_altitudearea.point1 = altitudearea.point1
|
|
|
|
|
new_altitudearea.point2 = altitudearea.point2
|
|
|
|
|
|
|
|
|
|
new_colors = {}
|
|
|
|
|
for color, areas in altitudearea.colors.items():
|
|
|
|
|
new_areas = {}
|
|
|
|
|
for access_restriction, area in areas.items():
|
|
|
|
|
if not new_geometry_prep.intersects(area):
|
|
|
|
|
continue
|
|
|
|
|
new_area = new_geometry.intersection(area)
|
|
|
|
|
if not new_area.is_empty:
|
|
|
|
|
new_areas[access_restriction] = new_area
|
|
|
|
|
if new_areas:
|
|
|
|
|
new_colors[color] = new_areas
|
|
|
|
|
new_altitudearea.colors = new_colors
|
|
|
|
|
|
2019-12-22 20:51:47 +01:00
|
|
|
|
new_altitudearea_obstacles = {}
|
|
|
|
|
for height, height_obstacles in altitudearea.obstacles.items():
|
|
|
|
|
new_height_obstacles = {}
|
|
|
|
|
for color, color_obstacles in height_obstacles.items():
|
|
|
|
|
new_color_obstacles = []
|
|
|
|
|
for obstacle in color_obstacles:
|
|
|
|
|
if new_geometry_prep.intersects(obstacle):
|
2023-11-11 13:30:45 +01:00
|
|
|
|
new_color_obstacles.append(
|
|
|
|
|
obstacle.intersection(unwrap_geom(altitudearea.geometry))
|
|
|
|
|
)
|
2019-12-22 20:51:47 +01:00
|
|
|
|
if new_color_obstacles:
|
|
|
|
|
new_height_obstacles[color] = new_color_obstacles
|
|
|
|
|
if new_height_obstacles:
|
|
|
|
|
new_altitudearea_obstacles[height] = new_height_obstacles
|
|
|
|
|
new_altitudearea.obstacles = new_altitudearea_obstacles
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
new_geoms.altitudeareas.append(new_altitudearea)
|
|
|
|
|
|
|
|
|
|
if new_geoms.walls.is_empty and not new_geoms.altitudeareas:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
new_geoms.ramps = tuple(
|
2023-11-07 20:26:18 +01:00
|
|
|
|
ramp for ramp in (crop_to.intersection(unwrap_geom(ramp)) for ramp in old_geoms.ramps)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
if not ramp.is_empty
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
new_geoms.heightareas = tuple(
|
2023-11-07 20:26:18 +01:00
|
|
|
|
(area, height) for area, height in ((crop_to.intersection(unwrap_geom(area)), height)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
for area, height in old_geoms.heightareas)
|
|
|
|
|
if not area.is_empty
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
new_geoms.affected_area = unary_union((
|
|
|
|
|
*(altitudearea.geometry for altitudearea in new_geoms.altitudeareas),
|
2023-12-24 03:24:47 +01:00
|
|
|
|
crop_to.intersection(new_geoms.walls.buffer(1)),
|
|
|
|
|
*((new_geoms.holes.buffer(1),) if new_geoms.holes else ()),
|
2017-11-20 02:28:08 +01:00
|
|
|
|
))
|
|
|
|
|
|
2017-11-20 17:21:19 +01:00
|
|
|
|
for access_restriction, area in old_geoms.access_restriction_affected.items():
|
2017-11-20 02:28:08 +01:00
|
|
|
|
new_area = crop_to.intersection(area)
|
|
|
|
|
if not new_area.is_empty:
|
2017-11-20 17:21:19 +01:00
|
|
|
|
access_restriction_affected.setdefault(access_restriction, []).append(new_area)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
new_geoms.restricted_spaces_indoors = {}
|
|
|
|
|
for access_restriction, area in old_geoms.restricted_spaces_indoors.items():
|
|
|
|
|
new_area = crop_to.intersection(area)
|
|
|
|
|
if not new_area.is_empty:
|
|
|
|
|
new_geoms.restricted_spaces_indoors[access_restriction] = new_area
|
|
|
|
|
|
|
|
|
|
new_geoms.restricted_spaces_outdoors = {}
|
|
|
|
|
for access_restriction, area in old_geoms.restricted_spaces_outdoors.items():
|
|
|
|
|
new_area = crop_to.intersection(area)
|
|
|
|
|
if not new_area.is_empty:
|
|
|
|
|
new_geoms.restricted_spaces_outdoors[access_restriction] = new_area
|
|
|
|
|
|
|
|
|
|
new_geoms.pk = old_geoms.pk
|
|
|
|
|
new_geoms.on_top_of_id = old_geoms.on_top_of_id
|
|
|
|
|
new_geoms.short_label = old_geoms.short_label
|
|
|
|
|
new_geoms.base_altitude = old_geoms.base_altitude
|
|
|
|
|
new_geoms.default_height = old_geoms.default_height
|
|
|
|
|
new_geoms.door_height = old_geoms.door_height
|
|
|
|
|
new_geoms.min_altitude = (min(area.altitude for area in new_geoms.altitudeareas)
|
|
|
|
|
if new_geoms.altitudeareas else new_geoms.base_altitude)
|
2018-12-06 17:37:54 +01:00
|
|
|
|
new_geoms.max_altitude = (max(area.altitude for area in new_geoms.altitudeareas)
|
|
|
|
|
if new_geoms.altitudeareas else new_geoms.base_altitude)
|
|
|
|
|
new_geoms.max_height = (min(height for area, height in new_geoms.heightareas)
|
|
|
|
|
if new_geoms.heightareas else new_geoms.default_height)
|
|
|
|
|
new_geoms.lower_bound = old_geoms.lower_bound
|
|
|
|
|
new_geoms.upper_bound = old_geoms.upper_bound
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
new_geoms.build_mesh(interpolators.get(render_level.pk) if level.pk == render_level.pk else None)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
render_data.levels.append(new_geoms)
|
|
|
|
|
|
2017-11-20 17:21:19 +01:00
|
|
|
|
access_restriction_affected = {
|
2017-11-20 02:28:08 +01:00
|
|
|
|
access_restriction: unary_union(areas)
|
2017-11-20 17:21:19 +01:00
|
|
|
|
for access_restriction, areas in access_restriction_affected.items()
|
2017-11-20 02:28:08 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-20 21:10:03 +01:00
|
|
|
|
access_restriction_affected = AccessRestrictionAffected.build(access_restriction_affected)
|
2023-12-07 20:11:55 +01:00
|
|
|
|
access_restriction_affected.save_level(render_level.pk, 'composite')
|
2017-11-20 21:10:03 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
map_history.save_level(render_level.pk, 'composite')
|
2017-11-20 21:10:03 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
package.add_level(render_level.pk, map_history, access_restriction_affected)
|
2017-11-20 17:21:19 +01:00
|
|
|
|
|
2023-12-07 20:11:55 +01:00
|
|
|
|
render_data.save(render_level.pk)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2017-11-20 21:10:03 +01:00
|
|
|
|
package.save_all()
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
2023-12-07 17:28:13 +01:00
|
|
|
|
cached = LocalContext()
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _level_filename(pk):
|
2023-11-11 12:06:46 +01:00
|
|
|
|
return settings.CACHE_ROOT / ('render_data_level_%d.pickle' % pk)
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def get(cls, level):
|
2017-12-15 00:02:40 +01:00
|
|
|
|
# get the current render data from local variable if no new processed mapupdate exists.
|
|
|
|
|
# this is much faster than any other possible cache
|
2023-12-07 17:28:13 +01:00
|
|
|
|
cache_key = MapUpdate.current_processed_cache_key()
|
|
|
|
|
level_pk = str(level.pk if isinstance(level, Level) else level)
|
2023-12-07 18:57:40 +01:00
|
|
|
|
if getattr(cls.cached, 'key', None) != cache_key:
|
2023-12-07 17:28:13 +01:00
|
|
|
|
cls.cached.key = cache_key
|
|
|
|
|
cls.cached.data = {}
|
|
|
|
|
else:
|
|
|
|
|
result = cls.cached.data.get(level_pk, None)
|
|
|
|
|
if result is not None:
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
pk = level.pk if isinstance(level, Level) else level
|
|
|
|
|
result = pickle.load(open(cls._level_filename(pk), 'rb'))
|
|
|
|
|
|
|
|
|
|
cls.cached.data[level_pk] = result
|
|
|
|
|
return result
|
2017-11-20 02:28:08 +01:00
|
|
|
|
|
|
|
|
|
def save(self, pk):
|
|
|
|
|
return pickle.dump(self, open(self._level_filename(pk), 'wb'))
|