Skip to content

Commit

Permalink
fix flood realtime bugs:
Browse files Browse the repository at this point in the history
- standardize color mapping in landing page: issue inasafe/inasafe-realtime#48
- default highlight latest flood report: issue inasafe/inasafe-realtime#42
- improvements for flood table: issue inasafe/inasafe-realtime#11
  • Loading branch information
lucernae committed Feb 6, 2017
1 parent 0f50b77 commit 4aac87c
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# coding=utf-8
import datetime

import pytz
from django.core.management.base import BaseCommand
from realtime.tasks.flood import recalculate_impact_info

from realtime.models.flood import Flood

__author__ = 'Rizky Maulana Nugraha "lucernae" <[email protected]>'
__date__ = '07/02/17'


class Command(BaseCommand):
"""Script to recalculate impact data.
"""
help = (
'Command to re-calculate total affected and boundary flooded in '
'flood.')

def handle(self, *args, **options):
using_range = False
if len(args) == 3:
if args[0] == 'range':
using_range = True
Command.recalculate_flood_from_range(args[1], args[2])

if len(args) > 0 and not using_range:
for a in args:
print 'Process flood : %s' % a
flood = Flood.objects.get(event_id=a)
try:
recalculate_impact_info(flood)
except Exception as e:
print e

elif not using_range:
floods = Flood.objects.all().order_by('-time')
print 'Process flood (%s)' % len(floods)
for flood in floods:
try:
recalculate_impact_info(flood)
except Exception as e:
print e

@staticmethod
def recalculate_flood_from_range(start_event_id, end_event_id):
format_str = '%Y%m%d%H-6-rw'
start_time = datetime.datetime.strptime(start_event_id, format_str)
if not end_event_id == 'now':
end_time = datetime.datetime.strptime(end_event_id, format_str)
else:
end_time = datetime.datetime.utcnow()
# convert to UTC
start_time = start_time.replace(tzinfo=pytz.UTC)
end_time = end_time.replace(tzinfo=pytz.UTC)
time_diff = end_time - start_time
total_hours = int(time_diff.total_seconds() / 3600)
success = 0
failed = 0
for i in range(0, total_hours):
hour_diff = datetime.timedelta(hours=i + 1)
target_time = start_time + hour_diff
event_id = target_time.strftime(format_str)
try:
print 'Processing flood: %s' % event_id
flood = Flood.objects.get(event_id=event_id)
recalculate_impact_info(flood)
success += 1
except Exception as e:
failed += 1
print e

print 'Recalculate process done'
print 'Success: %s. Failed: %s' % (success, failed)
26 changes: 26 additions & 0 deletions django_project/realtime/migrations/0025_auto_20170206_2046.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('realtime', '0024_auto_20170206_2044'),
]

operations = [
migrations.AddField(
model_name='flood',
name='boundary_flooded',
field=models.IntegerField(default=0, help_text=b'Total boundary affected by flood', verbose_name=b'Total boundary flooded'),
preserve_default=True,
),
migrations.AddField(
model_name='flood',
name='total_affected',
field=models.IntegerField(default=0, help_text=b'Total affected people by flood', verbose_name=b'Total affected people by flood'),
preserve_default=True,
),
]
8 changes: 8 additions & 0 deletions django_project/realtime/models/flood.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ class Meta:
through='FloodEventBoundary',
verbose_name='Flooded Boundaries',
help_text='The linked boundaries flooded by this event')
total_affected = models.IntegerField(
verbose_name='Total affected people by flood',
help_text='Total affected people by flood',
default=0)
boundary_flooded = models.IntegerField(
verbose_name='Total boundary flooded',
help_text='Total boundary affected by flood',
default=0)

objects = models.GeoManager()

Expand Down
43 changes: 42 additions & 1 deletion django_project/realtime/serializers/flood_serializer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# coding=utf-8

import pytz
from django.core.urlresolvers import reverse
from rest_framework import serializers
from realtime.models.flood import Flood, FloodReport
Expand Down Expand Up @@ -96,12 +96,53 @@ def get_url(self, serializer_field, obj):
# auto bind to get_url method
url = CustomSerializerMethodField()

def get_time_description(self, serializer_field, obj):
"""
:param serializer_field:
:type serializer_field: CustomSerializerMethodField
:param obj:
:type obj: Flood
:return:
"""
utc_time = obj.time.replace(tzinfo=pytz.utc)
jakarta_time = utc_time.astimezone(tz=pytz.timezone('Asia/Jakarta'))
description_format = '{interval} hour report for %d %B %Y at %H:%M:%S'.format(
interval=obj.interval)
return jakarta_time.strftime(description_format)

# auto bind to get_time_description method
time_description = CustomSerializerMethodField()

def get_event_id_formatted(self, serializer_field, obj):
"""
:param serializer_field:
:type serializer_field: CustomSerializerMethodField
:param obj:
:type obj: Flood
:return:
"""
utc_time = obj.time.replace(tzinfo=pytz.utc)
jakarta_time = utc_time.astimezone(tz=pytz.timezone('Asia/Jakarta'))
event_id_format = '%Y%m%d%H%M%S'
return jakarta_time.strftime(event_id_format)

# auto bind to get_event_id_formatted method
event_id_formatted = CustomSerializerMethodField()

class Meta:
model = Flood
fields = (
'url',
'event_id',
'event_id_formatted',
'time',
'time_description',
'total_affected',
'boundary_flooded',
'interval',
'source',
'region',
Expand Down
23 changes: 17 additions & 6 deletions django_project/realtime/signals/flood.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

from realtime.app_settings import LOGGER_NAME
from realtime.models.flood import Flood
from realtime.tasks.flood import process_hazard_layer, process_impact_layer
from realtime.tasks.flood import (
process_hazard_layer,
process_impact_layer,
recalculate_impact_info)

__author__ = 'Rizky Maulana Nugraha <[email protected]>'
__date__ = '12/4/15'
Expand All @@ -20,12 +23,20 @@


@receiver(post_save, sender=Flood)
def flood_post_save(sender, **kwargs):
def flood_post_save(sender, instance, created=None, update_fields=None, **kwargs):
"""Extract impact layer of the flood"""
try:
instance = kwargs.get('instance')
chain(
process_hazard_layer.si(instance),
process_impact_layer.si(instance))()
fields = ['total_affected', 'boundary_flooded']
update_fields = update_fields or []
for field in fields:
# if total_affected or boundary_flooded is updated,
# do not recalculate
if field in update_fields:
break
else:
chain(
process_hazard_layer.si(instance),
process_impact_layer.si(instance),
recalculate_impact_info(instance))()
except Exception as e:
LOGGER.exception(e)
43 changes: 39 additions & 4 deletions django_project/realtime/tasks/flood.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from django.contrib.gis.geos.collections import MultiPolygon
from django.contrib.gis.geos.geometry import GEOSGeometry
from django.contrib.gis.geos.polygon import Polygon
from django.db.models import F, FloatField, Sum

from realtime.app_settings import LOGGER_NAME
from realtime.models.flood import (
Expand Down Expand Up @@ -65,6 +66,9 @@ def process_hazard_layer(flood):

FloodEventBoundary.objects.filter(flood=flood).delete()

kelurahan = BoundaryAlias.objects.get(alias=OSM_LEVEL_7_NAME)
rw = BoundaryAlias.objects.get(alias=OSM_LEVEL_8_NAME)

for feat in layer:
pkey = feat.get('pkey')
level_name = feat.get('level_name')
Expand All @@ -81,7 +85,6 @@ def process_hazard_layer(flood):
geos_geometry = MultiPolygon(geos_geometry)

# check parent exists
kelurahan = BoundaryAlias.objects.get(alias=OSM_LEVEL_7_NAME)
try:
boundary_kelurahan = Boundary.objects.get(
name__iexact=parent_name.strip(),
Expand All @@ -94,7 +97,6 @@ def process_hazard_layer(flood):
boundary_alias=kelurahan)
boundary_kelurahan.save()

rw = BoundaryAlias.objects.get(alias=OSM_LEVEL_8_NAME)
try:
boundary_rw = Boundary.objects.get(
upstream_id=pkey, boundary_alias=rw)
Expand Down Expand Up @@ -161,6 +163,9 @@ def process_impact_layer(flood):
layer = source[0]

ImpactEventBoundary.objects.filter(flood=flood).delete()

kelurahan = BoundaryAlias.objects.get(alias=OSM_LEVEL_7_NAME)

for feat in layer:
level_7_name = feat.get('NAMA_KELUR').strip()
hazard_class = feat.get('affected')
Expand All @@ -175,8 +180,6 @@ def process_impact_layer(flood):
if hazard_class <= 1:
continue

kelurahan = BoundaryAlias.objects.get(alias=OSM_LEVEL_7_NAME)

try:
boundary_kelurahan = Boundary.objects.get(
name__iexact=level_7_name,
Expand Down Expand Up @@ -204,6 +207,38 @@ def process_impact_layer(flood):
return True


@app.task(queue='inasafe-django')
def recalculate_impact_info(flood):
"""Recalculate flood impact data.
:param flood: Flood object
:type flood: realtime.models.flood.Flood
"""
# calculate total boundary flooded in RW level
rw = BoundaryAlias.objects.get(alias=OSM_LEVEL_8_NAME)
boundary_flooded = FloodEventBoundary.objects.filter(
flood=flood,
boundary__boundary_alias=rw).count()
flood.boundary_flooded = boundary_flooded or 0

# calculate total population affected in kelurahan level
kelurahan = BoundaryAlias.objects.get(alias=OSM_LEVEL_7_NAME)
total_population_affected = ImpactEventBoundary.objects.filter(
flood=flood,
parent_boundary__boundary_alias=kelurahan
).aggregate(
total_population_affected=Sum('population_affected'))
try:
flood.total_affected = total_population_affected[
'total_population_affected'] or 0
except KeyError:
flood.total_affected = 0
pass

# prevent infinite recursive save
flood.save(update_fields=['total_affected', 'boundary_flooded'])


@app.task(queue='inasafe-django')
def create_flood_report():
process_flood.delay()
Expand Down
10 changes: 7 additions & 3 deletions django_project/realtime/templates/realtime/flood/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ <h3 class="panel-title">{% trans "Flood" %}</h3>
<thead>
<tr>
<th data-dynatable-column="event_id" style="display: none">{% trans "Event ID" %}</th>
<th data-dynatable-column="time">{% trans "Time" %}</th>
<th data-dynatable-column="event_id_formatted">{% trans "Event ID" %}</th>
<th data-dynatable-column="time_description">{% trans "Time" %}</th>
<th data-dynatable-column="total_affected">{% trans "Population Affected" %}</th>
<th data-dynatable-column="boundary_flooded">{% trans "RW Flooded" %}</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -150,7 +153,7 @@ <h3 class="panel-title">{% trans "Flood" %}</h3>

dynatable.settings.dataset.originalRecords = jsonTableContents;
dynatable.paginationPerPage.set(100);
dynatable.sorts.add('time', -1);
dynatable.sorts.add('event_id_formatted', -1);
dynatable.process();

// programatically show first flood in row
Expand Down Expand Up @@ -196,7 +199,8 @@ <h3 class="panel-title">{% trans "Flood" %}</h3>
},
dataset: {
records: jsonTableContents,
sorts: {'magnitude': -1}
sorts: {'magnitude': -1},
search: false,
}
}).data('dynatable');

Expand Down
15 changes: 14 additions & 1 deletion django_project/realtime/views/flood.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import datetime, timedelta
from django.core.exceptions import ValidationError, MultipleObjectsReturned
from django.db.models.aggregates import Count
from django.db.models import Q
from django.db.utils import IntegrityError
from django.http.response import JsonResponse, HttpResponseServerError
from django.shortcuts import render_to_response
Expand Down Expand Up @@ -114,7 +115,6 @@ class FloodList(mixins.ListModelMixin, mixins.CreateModelMixin,
search_fields = ('event_id', )
ordering = ('event_id', )
permission_classes = (DjangoModelPermissionsOrAnonReadOnly, )
# pagination_class = Pagina

def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
Expand Down Expand Up @@ -286,9 +286,22 @@ def delete(self, request, event_id, language):


class FloodEventList(FloodList):
# only retrieve for 6 interval hours
serializer_class = FloodSerializer
pagination_class = None

def get_queryset(self):
"""Return only 6-interval hours.
:return:
"""
# this interval is specific for Jakarta (GMT+07)
# it will show up as 6 hourly flood data from 00:00
query = (
Q(time__hour=23) | Q(time__hour=5) |
Q(time__hour=11) | Q(time__hour=17))
return Flood.objects.filter(query)


def flood_event_features(request, event_id):
try:
Expand Down

0 comments on commit 4aac87c

Please sign in to comment.