diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2016-05-29 22:47:48 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2016-05-29 22:47:48 +0200 |
commit | 80682cd167947fe31254643d09a2d9377ce96ef4 (patch) | |
tree | 5494374bd270631740a7887ef8d92f332fc28304 /chimere/models.py | |
parent | c3f712fd117c9dc92572395a394838e7582e0372 (diff) | |
download | Chimère-80682cd167947fe31254643d09a2d9377ce96ef4.tar.bz2 Chimère-80682cd167947fe31254643d09a2d9377ce96ef4.zip |
Add weight for clustering and heatmap
Diffstat (limited to 'chimere/models.py')
-rw-r--r-- | chimere/models.py | 100 |
1 files changed, 97 insertions, 3 deletions
diff --git a/chimere/models.py b/chimere/models.py index 8ab0b22..48e7f4b 100644 --- a/chimere/models.py +++ b/chimere/models.py @@ -36,7 +36,7 @@ from django.conf import settings from django.contrib.auth.models import User, Permission, ContentType, Group from django.contrib.gis.db import models from django.core.files import File -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.core.urlresolvers import reverse from django.db.models import Q, Count from django.db.models.signals import post_save, pre_save, m2m_changed @@ -267,6 +267,8 @@ class SubCategory(models.Model): blank=True, null=True) as_layer = models.BooleanField(_(u"Displayed in the layer menu"), default=False) + weight_formula = models.TextField(_(u"Weight formula"), default="", + blank=True, null=True) routing_warn = models.BooleanField(_(u"Routing warn"), default=False) order = models.IntegerField(_(u"Order"), default=1000) keywords = models.TextField(_(u"Keywords"), max_length=200, @@ -529,6 +531,15 @@ class GeographicItem(models.Model): _(u"End date"), blank=True, null=True, help_text=_(u"Not mandatory. Set it only if you have a multi-day " u"event. Format YYYY-MM-DD")) + weight = models.FloatField( + _(u"Weight"), blank=True, null=True, + help_text=_( + u"Weight are used for heatmap and clustering. A formula must " + u"defined in the associated category.")) + normalised_weight = models.FloatField( + _(u"Normalised weight"), blank=True, null=True, + help_text=_(u"The weight normalised to be between 0 and 1. " + u"Automatically recalculated.")) class Meta: abstract = True @@ -551,6 +562,9 @@ class GeographicItem(models.Model): setattr(self, attr_name + '_set', property_setter(self.__class__, pm)) + def get_geometry(self): + return getattr(self, self.geom_attr) + @property def geometry(self): return getattr(self, self.geom_attr).wkt @@ -689,6 +703,64 @@ class GeographicItem(models.Model): for pict in self.pictures.all()] return picts + def get_full_dict(self): + dct = {} + # get all property even the one not displayed + for pm in PropertyModel.objects.all(): + dct[pm.slug] = unicode(self.getProperty(pm)) + return dct + + def calculate_weight(self, formula): + try: + # try to eval the formula + # safe because open to admin only + return round(eval(formula.format(**self.get_full_dict())), 10) + except: + return 0 + + def get_weight_formula(self, get_associated_cat=False): + for sub in self.categories.order_by('order').all(): + if sub.weight_formula: + if get_associated_cat: + return sub.weight_formula, sub + return sub.weight_formula + if get_associated_cat: + return None, None + return None + + def normalise_weight(self): + formula, cat = self.get_weight_formula(get_associated_cat=True) + if not formula: + return + q = self.__class__.objects.filter( + categories__pk=cat.pk, weight__isnull=False) + if not q.count(): + return 0 + min_weight = q.order_by('weight')[0].weight or 0 + max_weight = q.order_by('-weight')[0].weight or 0 + return 1 - round( + (max_weight - self.weight or 0) / + (float((max_weight - min_weight)) or 1), 5) + + +def weighted_post_save(sender, **kwargs): + if not kwargs['instance']: + return + obj = kwargs['instance'] + formula = obj.get_weight_formula() + weight, normalised_weight = None, None + if formula: + weight = obj.calculate_weight(formula) + if weight != obj.weight: + obj.weight = weight + obj.save() + return + normalised_weight = obj.normalise_weight() + if weight != obj.weight or normalised_weight != obj.normalised_weight: + obj.weight = weight + obj.normalised_weight = normalised_weight + obj.save() + def property_setter(cls, propertymodel): def setter(self, value): @@ -880,6 +952,7 @@ def geometry_post_save(pre_save_geom_values): def marker_post_save(sender, **kwargs): + weighted_post_save(sender, **kwargs) if not kwargs['instance'] or kwargs['created']: return geometry_post_save(pre_save_marker_values)(sender, **kwargs) @@ -971,6 +1044,15 @@ class Polygon(GeographicItem): added.append(polygon['pk']) return vals + def get_full_dict(self): + dct = super(Polygon, self).get_full_dict() + # to be done - use local unity + dct['area'] = self.polygon.area + dct['length'] = self.polygon.length + return dct + +post_save.connect(weighted_post_save, sender=Polygon) + class MultimediaType(models.Model): MEDIA_TYPES = (('A', _(u"Audio")), @@ -1362,6 +1444,12 @@ class Route(GeographicItem): "color": color}} return json.dumps(attributes) + def get_full_dict(self): + dct = super(Route, self).get_full_dict() + # to be done - use local unity + dct['length'] = self.route.length + return dct + pre_save_route_values = {} @@ -1376,6 +1464,7 @@ def route_post_save(sender, **kwargs): if not kwargs['instance']: return geometry_post_save(pre_save_route_values)(sender, **kwargs) + weighted_post_save(sender, **kwargs) instance = kwargs['instance'] # manage associated marker @@ -1387,8 +1476,13 @@ def route_post_save(sender, **kwargs): if k in route_fields and k not in ('id', 'ref_item_id')]) marker_dct['point'] = "SRID=%d;POINT(%f %f)" % ( instance.route.srid, instance.route[0][0], instance.route[0][1]) - marker, created = Marker.objects.get_or_create(route=instance, - defaults=marker_dct) + try: + marker, created = Marker.objects.get_or_create(route=instance, + defaults=marker_dct) + except MultipleObjectsReturned: + # db error - trying to continue... + marker = Marker.objects.filter(route=instance).all()[0] + created = False if not created: marker.status = instance.status marker.point = marker_dct['point'] |