separate overlay feature data and geometries into different api endpoints so they can be cached independently
This commit is contained in:
parent
0e19ce5dac
commit
e5ac1e12df
7 changed files with 154 additions and 66 deletions
|
@ -91,7 +91,6 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
|
||||||
can_edit_changeset = request.changeset.can_edit(request)
|
can_edit_changeset = request.changeset.can_edit(request)
|
||||||
|
|
||||||
obj = None
|
obj = None
|
||||||
edit_utils = DefaultEditUtils(request)
|
|
||||||
if pk is not None:
|
if pk is not None:
|
||||||
# Edit existing map item
|
# Edit existing map item
|
||||||
kwargs = {'pk': pk}
|
kwargs = {'pk': pk}
|
||||||
|
|
|
@ -27,8 +27,9 @@ from c3nav.mapdata.schemas.models import (AccessRestrictionGroupSchema, AccessRe
|
||||||
LineObstacleSchema, LocationGroupCategorySchema, LocationGroupSchema,
|
LineObstacleSchema, LocationGroupCategorySchema, LocationGroupSchema,
|
||||||
ObstacleSchema, POISchema, RampSchema, SourceSchema, SpaceSchema, StairSchema,
|
ObstacleSchema, POISchema, RampSchema, SourceSchema, SpaceSchema, StairSchema,
|
||||||
DataOverlaySchema, DataOverlayFeatureSchema, LocationRedirectSchema,
|
DataOverlaySchema, DataOverlayFeatureSchema, LocationRedirectSchema,
|
||||||
WayTypeSchema,
|
WayTypeSchema, DataOverlayFeatureGeometrySchema,
|
||||||
DataOverlayFeatureUpdateSchema, DataOverlayFeatureBulkUpdateSchema)
|
DataOverlayFeatureUpdateSchema, DataOverlayFeatureBulkUpdateSchema,
|
||||||
|
)
|
||||||
|
|
||||||
mapdata_api_router = APIRouter(tags=["mapdata"])
|
mapdata_api_router = APIRouter(tags=["mapdata"])
|
||||||
|
|
||||||
|
@ -77,14 +78,16 @@ class MapdataEndpoint:
|
||||||
model: Type[Model]
|
model: Type[Model]
|
||||||
schema: Type[BaseSchema]
|
schema: Type[BaseSchema]
|
||||||
filters: Type[FilterSchema] | None = None
|
filters: Type[FilterSchema] | None = None
|
||||||
|
no_cache: bool = False
|
||||||
|
name: Optional[str] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model_name(self):
|
def model_name(self):
|
||||||
return self.model._meta.model_name
|
return self.model._meta.model_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model_name_plural(self):
|
def endpoint_name(self):
|
||||||
return self.model._meta.default_related_name
|
return self.name if self.name is not None else self.model._meta.default_related_name
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -110,7 +113,7 @@ class MapdataAPIBuilder:
|
||||||
add_call_params = {}
|
add_call_params = {}
|
||||||
call_param_values = set(add_call_params.values())
|
call_param_values = set(add_call_params.values())
|
||||||
call_params = (
|
call_params = (
|
||||||
*(f"{name}={name}" for name in set(view_params.keys())-call_param_values),
|
*(f"{name}={name}" for name in set(view_params.keys()) - call_param_values),
|
||||||
*(f"{name}={value}" for name, value in add_call_params.items()),
|
*(f"{name}={value}" for name, value in add_call_params.items()),
|
||||||
)
|
)
|
||||||
method_code = "\n".join((
|
method_code = "\n".join((
|
||||||
|
@ -139,12 +142,15 @@ class MapdataAPIBuilder:
|
||||||
)
|
)
|
||||||
list_func.__name__ = f"{endpoint.model_name}_list"
|
list_func.__name__ = f"{endpoint.model_name}_list"
|
||||||
|
|
||||||
self.router.get(f"/{endpoint.model_name_plural}/", summary=f"{endpoint.model_name} list",
|
if not endpoint.no_cache:
|
||||||
|
list_func = api_etag()(list_func)
|
||||||
|
|
||||||
|
self.router.get(f"/{endpoint.endpoint_name}/", summary=f"{endpoint.model_name} list",
|
||||||
tags=[f"mapdata-{tag}"], description=schema_description(endpoint.schema),
|
tags=[f"mapdata-{tag}"], description=schema_description(endpoint.schema),
|
||||||
response={200: list[endpoint.schema],
|
response={200: list[endpoint.schema],
|
||||||
**(validate_responses if endpoint.filters else {}),
|
**(validate_responses if endpoint.filters else {}),
|
||||||
**auth_responses})(
|
**auth_responses})(
|
||||||
api_etag()(list_func)
|
list_func
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_by_id_endpoint(self, endpoint: MapdataEndpoint, tag: str):
|
def add_by_id_endpoint(self, endpoint: MapdataEndpoint, tag: str):
|
||||||
|
@ -160,7 +166,7 @@ class MapdataAPIBuilder:
|
||||||
)
|
)
|
||||||
list_func.__name__ = f"{endpoint.model_name}_by_id"
|
list_func.__name__ = f"{endpoint.model_name}_by_id"
|
||||||
|
|
||||||
self.router.get(f'/{endpoint.model_name_plural}/{{{id_field}}}/', summary=f"{endpoint.model_name} by ID",
|
self.router.get(f'/{endpoint.endpoint_name}/{{{id_field}}}/', summary=f"{endpoint.model_name} by ID",
|
||||||
tags=[f"mapdata-{tag}"], description=schema_description(endpoint.schema),
|
tags=[f"mapdata-{tag}"], description=schema_description(endpoint.schema),
|
||||||
response={200: endpoint.schema, **API404.dict(), **auth_responses})(
|
response={200: endpoint.schema, **API404.dict(), **auth_responses})(
|
||||||
api_etag()(list_func)
|
api_etag()(list_func)
|
||||||
|
@ -227,6 +233,14 @@ mapdata_endpoints: dict[str, list[MapdataEndpoint]] = {
|
||||||
model=DataOverlayFeature,
|
model=DataOverlayFeature,
|
||||||
schema=DataOverlayFeatureSchema,
|
schema=DataOverlayFeatureSchema,
|
||||||
filters=ByOverlayFilter,
|
filters=ByOverlayFilter,
|
||||||
|
no_cache=True,
|
||||||
|
),
|
||||||
|
MapdataEndpoint(
|
||||||
|
model=DataOverlayFeature,
|
||||||
|
schema=DataOverlayFeatureGeometrySchema,
|
||||||
|
filters=ByOverlayFilter,
|
||||||
|
no_cache=True,
|
||||||
|
name='dataoverlayfeaturegeometries'
|
||||||
),
|
),
|
||||||
MapdataEndpoint(
|
MapdataEndpoint(
|
||||||
model=WayType,
|
model=WayType,
|
||||||
|
@ -304,12 +318,11 @@ mapdata_endpoints: dict[str, list[MapdataEndpoint]] = {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
MapdataAPIBuilder(router=mapdata_api_router).build_all_endpoints(mapdata_endpoints)
|
MapdataAPIBuilder(router=mapdata_api_router).build_all_endpoints(mapdata_endpoints)
|
||||||
|
|
||||||
|
|
||||||
@mapdata_api_router.post('/dataoverlayfeatures/{id}', summary="update a data overlay feature (including geometries)",
|
@mapdata_api_router.post('/dataoverlayfeatures/{id}', summary="update a data overlay feature (including geometries)",
|
||||||
response={204: None, **API404.dict(), **auth_permission_responses})
|
response={204: None, **API404.dict(), **auth_permission_responses})
|
||||||
def update_data_overlay_feature(request, id: int, parameters: DataOverlayFeatureUpdateSchema):
|
def update_data_overlay_feature(request, id: int, parameters: DataOverlayFeatureUpdateSchema):
|
||||||
"""
|
"""
|
||||||
update the data overlay feature
|
update the data overlay feature
|
||||||
|
@ -317,7 +330,8 @@ def update_data_overlay_feature(request, id: int, parameters: DataOverlayFeature
|
||||||
|
|
||||||
feature = get_object_or_404(DataOverlayFeature, id=id)
|
feature = get_object_or_404(DataOverlayFeature, id=id)
|
||||||
|
|
||||||
if feature.overlay.edit_access_restriction_id is None or feature.overlay.edit_access_restriction_id not in AccessPermission.get_for_request(request):
|
if feature.overlay.edit_access_restriction_id is None or feature.overlay.edit_access_restriction_id not in AccessPermission.get_for_request(
|
||||||
|
request):
|
||||||
raise APIPermissionDenied('You are not allowed to edit this object.')
|
raise APIPermissionDenied('You are not allowed to edit this object.')
|
||||||
|
|
||||||
updates = parameters.dict(exclude_unset=True)
|
updates = parameters.dict(exclude_unset=True)
|
||||||
|
@ -329,10 +343,10 @@ def update_data_overlay_feature(request, id: int, parameters: DataOverlayFeature
|
||||||
|
|
||||||
return 204, None
|
return 204, None
|
||||||
|
|
||||||
|
|
||||||
@mapdata_api_router.post('/dataoverlayfeatures-bulk', summary="bulk-update data overlays (including geometries)",
|
@mapdata_api_router.post('/dataoverlayfeatures-bulk', summary="bulk-update data overlays (including geometries)",
|
||||||
response={204: None, **API404.dict(), **auth_permission_responses})
|
response={204: None, **API404.dict(), **auth_permission_responses})
|
||||||
def update_data_overlay_features_bulk(request, parameters: DataOverlayFeatureBulkUpdateSchema):
|
def update_data_overlay_features_bulk(request, parameters: DataOverlayFeatureBulkUpdateSchema):
|
||||||
|
|
||||||
permissions = AccessPermission.get_for_request(request)
|
permissions = AccessPermission.get_for_request(request)
|
||||||
|
|
||||||
updates = {
|
updates = {
|
||||||
|
@ -343,7 +357,8 @@ def update_data_overlay_features_bulk(request, parameters: DataOverlayFeatureBul
|
||||||
forbidden_object_ids = []
|
forbidden_object_ids = []
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
features = DataOverlayFeature.objects.filter(id__in=updates.keys()).annotate(edit_access_restriction_id=F('overlay__edit_access_restriction_id'))
|
features = DataOverlayFeature.objects.filter(id__in=updates.keys()).annotate(
|
||||||
|
edit_access_restriction_id=F('overlay__edit_access_restriction_id'))
|
||||||
|
|
||||||
for feature in features:
|
for feature in features:
|
||||||
if feature.edit_access_restriction_id is None or feature.edit_access_restriction_id not in permissions:
|
if feature.edit_access_restriction_id is None or feature.edit_access_restriction_id not in permissions:
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 5.1.3 on 2024-12-26 19:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mapdata', '0130_dataoverlay_edit_access_restriction'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='dataoverlay',
|
||||||
|
name='update_interval',
|
||||||
|
field=models.PositiveIntegerField(blank=True, help_text='in seconds', null=True, verbose_name='frontend update interval'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dataoverlay',
|
||||||
|
name='fill_opacity',
|
||||||
|
field=models.FloatField(blank=True, null=True, verbose_name='default fill opacity'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -24,16 +24,21 @@ class DataOverlay(TitledMixin, AccessRestrictionMixin, models.Model):
|
||||||
stroke_width = models.FloatField(blank=True, null=True, verbose_name=_('default stroke width'))
|
stroke_width = models.FloatField(blank=True, null=True, verbose_name=_('default stroke width'))
|
||||||
stroke_opacity = models.FloatField(blank=True, null=True, verbose_name=_('stroke opacity'))
|
stroke_opacity = models.FloatField(blank=True, null=True, verbose_name=_('stroke opacity'))
|
||||||
fill_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default fill color'))
|
fill_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default fill color'))
|
||||||
fill_opacity = models.FloatField(blank=True, null=True, verbose_name=_('fill opacity'))
|
fill_opacity = models.FloatField(blank=True, null=True, verbose_name=_('default fill opacity'))
|
||||||
|
|
||||||
cluster_points = models.BooleanField(default=False, verbose_name=_('cluster points together when zoomed out'))
|
cluster_points = models.BooleanField(default=False, verbose_name=_('cluster points together when zoomed out'))
|
||||||
|
|
||||||
default_geomtype = models.CharField(max_length=255, blank=True, null=True, choices=GeometryType, verbose_name=_('default geometry type'))
|
default_geomtype = models.CharField(max_length=255, blank=True, null=True, choices=GeometryType,
|
||||||
|
verbose_name=_('default geometry type'))
|
||||||
|
|
||||||
pull_url = models.URLField(blank=True, null=True, verbose_name=_('pull URL'))
|
pull_url = models.URLField(blank=True, null=True, verbose_name=_('pull URL'))
|
||||||
pull_headers: dict[str, str] = SchemaField(schema=dict[str, str], null=True,
|
pull_headers: dict[str, str] = SchemaField(schema=dict[str, str], null=True,
|
||||||
verbose_name=_('headers for pull http request (JSON object)'))
|
verbose_name=_('headers for pull http request (JSON object)'))
|
||||||
pull_interval = models.DurationField(blank=True, null=True, verbose_name=_('pull interval'))
|
pull_interval = models.DurationField(blank=True, null=True, verbose_name=_('pull interval'))
|
||||||
|
|
||||||
|
update_interval = models.PositiveIntegerField(blank=True, null=True, verbose_name=_('frontend update interval'),
|
||||||
|
help_text=_('in seconds'))
|
||||||
|
|
||||||
edit_access_restriction = models.ForeignKey(AccessRestriction, null=True, blank=True,
|
edit_access_restriction = models.ForeignKey(AccessRestriction, null=True, blank=True,
|
||||||
related_name='edit_access_restrictions',
|
related_name='edit_access_restrictions',
|
||||||
verbose_name=_('Editor Access Restriction'),
|
verbose_name=_('Editor Access Restriction'),
|
||||||
|
@ -46,7 +51,8 @@ class DataOverlay(TitledMixin, AccessRestrictionMixin, models.Model):
|
||||||
|
|
||||||
|
|
||||||
class DataOverlayFeature(TitledMixin, LevelGeometryMixin, models.Model):
|
class DataOverlayFeature(TitledMixin, LevelGeometryMixin, models.Model):
|
||||||
overlay = models.ForeignKey('mapdata.DataOverlay', on_delete=models.CASCADE, verbose_name=_('Overlay'), related_name='features')
|
overlay = models.ForeignKey('mapdata.DataOverlay', on_delete=models.CASCADE, verbose_name=_('Overlay'),
|
||||||
|
related_name='features')
|
||||||
geometry = GeometryField()
|
geometry = GeometryField()
|
||||||
# level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level'), related_name='data_overlay_features')
|
# level = models.ForeignKey('mapdata.Level', on_delete=models.CASCADE, verbose_name=_('level'), related_name='data_overlay_features')
|
||||||
external_url = models.URLField(blank=True, null=True, verbose_name=_('external URL'))
|
external_url = models.URLField(blank=True, null=True, verbose_name=_('external URL'))
|
||||||
|
@ -62,9 +68,10 @@ class DataOverlayFeature(TitledMixin, LevelGeometryMixin, models.Model):
|
||||||
point_icon = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('point icon'),
|
point_icon = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('point icon'),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
'use this material icon to display points, instead of drawing a small circle (only makes sense if the geometry is a point)'))
|
'use this material icon to display points, instead of drawing a small circle (only makes sense if the geometry is a point)'))
|
||||||
extra_data: Optional[dict[str, str|int|bool]] = SchemaField(schema=dict[str, str|int|bool], blank=True, null=True,
|
extra_data: Optional[dict[str, str | int | bool]] = SchemaField(schema=dict[str, str | int | bool], blank=True,
|
||||||
default=None,
|
null=True,
|
||||||
verbose_name=_('extra data (JSON object)'))
|
default=None,
|
||||||
|
verbose_name=_('extra data (JSON object)'))
|
||||||
|
|
||||||
def to_geojson(self, instance=None) -> dict:
|
def to_geojson(self, instance=None) -> dict:
|
||||||
result = {
|
result = {
|
||||||
|
|
|
@ -376,14 +376,13 @@ class DataOverlaySchema(TitledSchema, DjangoModelSchema):
|
||||||
fill_color: Optional[str]
|
fill_color: Optional[str]
|
||||||
fill_opacity: Optional[float]
|
fill_opacity: Optional[float]
|
||||||
cluster_points: bool
|
cluster_points: bool
|
||||||
|
update_interval: Optional[PositiveInt]
|
||||||
|
|
||||||
|
|
||||||
|
class DataOverlayFeatureSchema(TitledSchema, DjangoModelSchema):
|
||||||
class DataOverlayFeatureSchema(TitledSchema, WithGeometrySchema, DjangoModelSchema):
|
|
||||||
"""
|
"""
|
||||||
A feature (any kind of geometry) to be displayed as part of a data overlay.
|
A feature (any kind of geometry) to be displayed as part of a data overlay.
|
||||||
"""
|
"""
|
||||||
geometry: AnyGeometrySchema
|
|
||||||
level_id: PositiveInt
|
level_id: PositiveInt
|
||||||
stroke_color: Optional[str]
|
stroke_color: Optional[str]
|
||||||
stroke_width: Optional[float]
|
stroke_width: Optional[float]
|
||||||
|
@ -397,6 +396,13 @@ class DataOverlayFeatureSchema(TitledSchema, WithGeometrySchema, DjangoModelSche
|
||||||
external_url: Optional[str]
|
external_url: Optional[str]
|
||||||
extra_data: Optional[dict[str, str | int | float]]
|
extra_data: Optional[dict[str, str | int | float]]
|
||||||
|
|
||||||
|
|
||||||
|
class DataOverlayFeatureGeometrySchema(WithGeometrySchema, DjangoModelSchema):
|
||||||
|
"""
|
||||||
|
A feature (any kind of geometry) to be displayed as part of a data overlay.
|
||||||
|
"""
|
||||||
|
geometry: AnyGeometrySchema
|
||||||
|
|
||||||
class DataOverlayFeatureUpdateSchema(BaseSchema):
|
class DataOverlayFeatureUpdateSchema(BaseSchema):
|
||||||
"""
|
"""
|
||||||
An update to a data overlay feature.
|
An update to a data overlay feature.
|
||||||
|
|
|
@ -1975,29 +1975,25 @@ blink {
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cluster {
|
.marker-cluster {
|
||||||
background-color: color-mix(in srgb, transparent, var(--color-primary) 60%);
|
span {
|
||||||
background-clip: padding-box;
|
display: inline-block;
|
||||||
border-radius: 20px;
|
|
||||||
|
|
||||||
div {
|
|
||||||
background-color: white;
|
|
||||||
color: var(--color-primary);
|
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
margin-left: 5px;
|
line-height: 30px;
|
||||||
margin-top: 5px;
|
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 15px;
|
background-color: white;
|
||||||
font-size: 12px;
|
color: var(--cluster-marker-color);
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 100%;
|
||||||
|
|
||||||
span {
|
box-shadow: 0 0 0 5px color-mix(in srgb, transparent, var(--cluster-marker-color) 60%);
|
||||||
line-height: 30px;
|
|
||||||
font-weight: bold;
|
transition: color, background-color 150ms ease-in-out;
|
||||||
}
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-primary);
|
background-color: var(--cluster-marker-color);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,17 @@
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
function makeClusterIconCreate(color) {
|
||||||
|
return function(cluster) {
|
||||||
|
const childCount = cluster.getChildCount();
|
||||||
|
return new L.DivIcon({
|
||||||
|
html: `<div style="--cluster-marker-color: ${color};"><span>${childCount}</span></div>`,
|
||||||
|
className: 'marker-cluster',
|
||||||
|
iconSize: new L.Point(30, 30)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a wrapper for localStorage, catching possible exception when accessing or setting data.
|
* a wrapper for localStorage, catching possible exception when accessing or setting data.
|
||||||
* working silently if there are errors apart from a console log message when setting an item.
|
* working silently if there are errors apart from a console log message when setting an item.
|
||||||
|
@ -1586,6 +1597,7 @@ c3nav = {
|
||||||
color: 'var(--color-primary)',
|
color: 'var(--color-primary)',
|
||||||
},
|
},
|
||||||
showCoverageOnHover: false,
|
showCoverageOnHover: false,
|
||||||
|
iconCreateFunction: makeClusterIconCreate('var(--color-primary)'),
|
||||||
}).addTo(layerGroup);
|
}).addTo(layerGroup);
|
||||||
}
|
}
|
||||||
c3nav._levelControl.finalize();
|
c3nav._levelControl.finalize();
|
||||||
|
@ -3130,6 +3142,7 @@ L.SquareGridLayer = L.Layer.extend({
|
||||||
|
|
||||||
class DataOverlay {
|
class DataOverlay {
|
||||||
levels = null;
|
levels = null;
|
||||||
|
feature_geometries = {};
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.id = options.id;
|
this.id = options.id;
|
||||||
|
@ -3141,19 +3154,66 @@ class DataOverlay {
|
||||||
this.default_stroke_opacity = options.stroke_opacity;
|
this.default_stroke_opacity = options.stroke_opacity;
|
||||||
this.default_fill_color = options.fill_color;
|
this.default_fill_color = options.fill_color;
|
||||||
this.default_fill_opacity = options.fill_opacity;
|
this.default_fill_opacity = options.fill_opacity;
|
||||||
|
this.update_interval = options.update_interval === null ? null : options.update_interval * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create() {
|
async create() {
|
||||||
const features = await c3nav_api.get(`mapdata/dataoverlayfeatures/?overlay=${this.id}`);
|
const [features, feature_geometries] = await Promise.all([
|
||||||
|
c3nav_api.get(`mapdata/dataoverlayfeatures/?overlay=${this.id}`),
|
||||||
|
c3nav_api.get(`mapdata/dataoverlayfeaturegeometries/?overlay=${this.id}`)
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.feature_geometries = Object.fromEntries(feature_geometries.map(f => [f.id, f.geometry]));
|
||||||
|
|
||||||
|
this.update_features(features);
|
||||||
|
|
||||||
|
if (this.update_interval !== null) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.fetch_features()
|
||||||
|
.catch(err => console.error(err))
|
||||||
|
}, this.update_interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch_features() {
|
||||||
|
const features= await c3nav_api.get(`mapdata/dataoverlayfeatures/?overlay=${this.id}`);
|
||||||
|
|
||||||
|
this.update_features(features);
|
||||||
|
|
||||||
|
if (this.update_interval !== null) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.fetch_features()
|
||||||
|
.catch(err => console.error(err))
|
||||||
|
}, this.update_interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_features (features) {
|
||||||
|
if (this.levels === null) {
|
||||||
|
this.levels = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let id in this.levels) {
|
||||||
|
this.levels[id].clearLayers();
|
||||||
|
}
|
||||||
|
|
||||||
const levels = {};
|
|
||||||
for (const feature of features) {
|
for (const feature of features) {
|
||||||
|
const geometry = this.feature_geometries[feature.id]
|
||||||
const level_id = feature.level_id;
|
const level_id = feature.level_id;
|
||||||
if (!(level_id in levels)) {
|
if (!(level_id in this.levels)) {
|
||||||
if (this.cluster_points) {
|
if (this.cluster_points) {
|
||||||
levels[level_id] = L.markerClusterGroup();
|
this.levels[level_id] = L.markerClusterGroup({
|
||||||
|
spiderLegPolylineOptions: {
|
||||||
|
color: this.default_stroke_color ?? 'var(--color-map-overlay)',
|
||||||
|
},
|
||||||
|
polygonOptions: {
|
||||||
|
color: this.default_stroke_color ?? 'var(--color-map-overlay)',
|
||||||
|
fillColor: this.default_fill_color ?? 'var(--color-map-overlay)',
|
||||||
|
},
|
||||||
|
iconCreateFunction: makeClusterIconCreate(this.default_fill_color ?? 'var(--color-map-overlay)'),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
levels[level_id] = L.layerGroup();
|
this.levels[level_id] = L.layerGroup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const style = {
|
const style = {
|
||||||
|
@ -3163,7 +3223,7 @@ class DataOverlay {
|
||||||
'fillColor': feature.fill_color ?? this.default_fill_color ?? 'var(--color-map-overlay)',
|
'fillColor': feature.fill_color ?? this.default_fill_color ?? 'var(--color-map-overlay)',
|
||||||
'fillOpacity': feature.fill_opacity ?? this.default_fill_opacity ?? 0.2,
|
'fillOpacity': feature.fill_opacity ?? this.default_fill_opacity ?? 0.2,
|
||||||
};
|
};
|
||||||
const layer = L.geoJson(feature.geometry, {
|
const layer = L.geoJson(geometry, {
|
||||||
style,
|
style,
|
||||||
interactive: feature.interactive,
|
interactive: feature.interactive,
|
||||||
pointToLayer: (geom, latlng) => {
|
pointToLayer: (geom, latlng) => {
|
||||||
|
@ -3176,22 +3236,6 @@ class DataOverlay {
|
||||||
iconAnchor: [12, 12],
|
iconAnchor: [12, 12],
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
if (feature.point_icon !== null) {
|
|
||||||
return L.marker(latlng, {
|
|
||||||
title: feature.title,
|
|
||||||
icon: L.divIcon({
|
|
||||||
className: 'symbol-icon ' + (feature.interactive ? 'symbol-icon-interactive' : ''),
|
|
||||||
html: `<span style="--icon-color: ${style.color}">${feature.point_icon}</span>`,
|
|
||||||
iconSize: [24, 24],
|
|
||||||
iconAnchor: [12, 12],
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return L.circleMarker(latlng, {
|
|
||||||
title: feature.title,
|
|
||||||
...style
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onEachFeature: (f, layer) => {
|
onEachFeature: (f, layer) => {
|
||||||
if (feature.interactive) {
|
if (feature.interactive) {
|
||||||
|
@ -3216,10 +3260,8 @@ class DataOverlay {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
levels[level_id].addLayer(layer);
|
this.levels[level_id].addLayer(layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.levels = levels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async enable(levels) {
|
async enable(levels) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue