summaryrefslogtreecommitdiff
path: root/chimere/models.py
diff options
context:
space:
mode:
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
commit80682cd167947fe31254643d09a2d9377ce96ef4 (patch)
tree5494374bd270631740a7887ef8d92f332fc28304 /chimere/models.py
parentc3f712fd117c9dc92572395a394838e7582e0372 (diff)
downloadChimè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.py100
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']