From e8a5430e2ecc1be16a697cf8099d01356d8b17b6 Mon Sep 17 00:00:00 2001 From: Dave Thau Date: Mon, 23 Jan 2012 12:31:51 -0800 Subject: [PATCH 1/3] Adding kml reports, refactoring reporting in api.py, minor typo fixes --- src/application/report_types.py | 192 ++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100755 src/application/report_types.py diff --git a/src/application/report_types.py b/src/application/report_types.py new file mode 100755 index 0000000..ec72ead --- /dev/null +++ b/src/application/report_types.py @@ -0,0 +1,192 @@ +""" +report_types.py + +Classes for describing different report formats. + +""" + +import logging +import csv +from application import settings +from google.appengine.ext import deferred +from datetime import datetime, date +from dateutil.relativedelta import relativedelta +from ft import FT +from flask import Response, abort, request +from StringIO import StringIO +from models import FustionTablesNames, StatsStore, FusionTablesPolygons + + +class ReportType(object): + + zone = None + + def init(self, zone): + self.f.truncate(0) + self.zone = zone + + def set_zone(self): + self.zone = zone + + def write_row(self, report, table=None, kml=None): + raise NotImplementedError + + def write_header(self): + raise NotImplementedError + + def write_footer(self): + raise NotImplementedError + + def value(self): + raise NotImplementedError + + def response(self, file_name): + raise NotImplementedError + + def get_stats(self, report, table): + report_id = str(report.key()) + st = StatsStore.get_for_report(report_id) + if not st: + logging.error("no cached stats for %s" % report_id) + abort(404) + + if self.zone: + stats = st.table_accum(table, self.zone) + if not stats: + logging.error("no stats for %s" % report_id) + abort(404) + else: + stats = st.for_table(table) + if not stats: + logging.error("no stats for %s" % report_id) + abort(404) + return stats + + @staticmethod + def factory(format): + if (format == "kml"): + return KMLReportType() + else: + return CSVReportType() + + + +class CSVReportType(ReportType): + + f = StringIO() + csv_file = csv.writer(f) + + def write_header(self): + if self.zone: + self.csv_file.writerow(('report_id', 'start_date', 'end_date', 'deforested', 'degraded')) + else: + self.csv_file.writerow(('report_id', 'start_date', 'end_date', 'zone_id', 'deforested', 'degraded')) + + def write_footer(self): + pass + + def write_row(self, report, stats, table=None, kml=None): + name = None + + if table and not self.zone: + table_names = FustionTablesNames.all().filter('table_id =', table).fetch(1)[0].as_dict() + name = table_names.get(stats['id'], stats['id']) + + if name: + self.csv_file.writerow((str(report.key().id()), + report.start.isoformat(), + report.end.isoformat(), + name, + stats['def'], + stats['deg'])) + else: + self.csv_file.writerow((str(report.key().id()), + report.start.isoformat(), + report.end.isoformat(), + stats['def'], + stats['deg'])) + + + def value(self): + return self.f.getvalue() + + def response(self, file_name): + result = self.value() + self.f.truncate(0) + return Response(result, + headers={ + "Content-Disposition": "attachment; filename=\"" + file_name + ".csv\"" + }, + mimetype='text/csv') + +class KMLReportType(ReportType): + + f = StringIO() + + def write_header(self): + self.f.write("") + self.f.write("") + self.f.write("") + self.f.write("") + + def write_footer(self): + self.f.write("") + self.f.write("") + + def write_row(self, report, stats, table=None, kml=None): + name = None + + if table: + table_names = FustionTablesNames.all().filter('table_id =', table).fetch(1)[0].as_dict() + name = table_names.get(stats['id'], stats['id']) + kml = self.kml(table, stats['id']) + else: + name = "Custom Polygon" + + description = self.description(name, stats) + + self.f.write("") + self.f.write("#transGreenPoly") + self.f.write("" + name + "") + self.f.write("" + description + "") + self.f.write(kml) + self.f.write("") + + def value(self): + return self.f.getvalue() + + def response(self, file_name): + result = self.value() + self.f.truncate(0) + return Response(result, + headers={ + "Content-Disposition": "attachment; filename=\"" + file_name + ".kml\"" + }, + mimetype='text/kml') + + def kml(self, table, row_id): + cl = FT(settings.FT_CONSUMER_KEY, + settings.FT_CONSUMER_SECRET, + settings.FT_TOKEN, + settings.FT_SECRET) + + #TODO: do this better + if (table == 1568452): + id = 'ex_area' + else: + id = 'name' + + info = cl.sql("select geometry from %s where %s = %s" % (table, id, row_id)) + polygon = info.split('\n')[1] + polygon = polygon.replace("\"", "") + return polygon + + def description(self, name, stats): + desc = "

" + name + "

Deforestation: " + str(stats['def']) + "km2Degradation: " + str(stats['deg']) + "km2]]>" + return desc + + + + + From e3df4d3d0073bbdeef9b6e8e8c5f815576b012c8 Mon Sep 17 00:00:00 2001 From: Dave Thau Date: Mon, 23 Jan 2012 12:42:36 -0800 Subject: [PATCH 2/3] Adding kml to reports --- README.md | 5 +- src/application/api.py | 114 ++++++++++------------------ src/application/ft.py | 1 - src/application/generate_reports.py | 52 +++++++------ src/application/models.py | 6 +- src/application/resources/report.py | 6 +- src/application/settings.py | 4 +- src/content/templates/404.html | 2 +- src/index.yaml | 5 ++ src/static/js/vis/report_dialog.js | 9 +-- src/static/js/vis/stats.js | 7 +- 11 files changed, 94 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 0f8389b..ade98dd 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,10 @@ The tool is intended to be use directly online (still pending the final URL) so * Go to the src folder cd `src` * Run it using the following script: `tools/start`. Leave the window open, the application should be running. 6. Create an initial report - * Open a new Terminal window, leaving the other open, and run `curl -d '' "http://localhost:8080/_ah/cmd/create_report?year=2011&month=7&day=15"` + * Open a new Terminal window, leaving the other open, + * Initialize fusion tables: curl "http://localhost:8080/_ah/cmd/fusion_tables_names" + * Create an unclosed report curl -d '' "http://localhost:8080/_ah/cmd/create_report?year=2011&month=7&day=15" + * If you'd like, create an closed report curl -d '' "http://localhost:8080/_ah/cmd/create_report?year=2011&month=8&day=15&fyear=2011&fmonth=9&fday=15&assetid=SAD_VALIDATED/SAD_2010_05" 7. Start using the app. * You should now be able to go to http://localhost:8080 and start using the application locally. * When loggin in dont forget to set yourself as admin. diff --git a/src/application/api.py b/src/application/api.py index 856580f..904f6b8 100755 --- a/src/application/api.py +++ b/src/application/api.py @@ -14,6 +14,8 @@ from flask import jsonify, request, abort, Response from app import app import settings +from report_types import ReportType, CSVReportType, KMLReportType +from kml import path_to_kml from models import Area, Note, Report, StatsStore, FustionTablesNames from ee import NDFI, EELandsat, Stats @@ -44,9 +46,10 @@ #TODO: this function needs a huge refactor -@app.route('/api/v0/stats//') +@app.route('/api/v0/stats/
//') +@app.route('/api/v0/stats/
/') @app.route('/api/v0/stats/
') -def stats(table, zone=None): +def stats(table, zone=None, format="csv"): reports = request.args.get('reports', None) if not reports: @@ -56,67 +59,33 @@ def stats(table, zone=None): except ValueError: logging.error("bad format for report id") abort(400) + + this_report = ReportType.factory(format) + this_report.init(zone) + this_report.write_header() - f = StringIO() - csv_file = csv.writer(f) + logging.info("table id is %s ", table) + logging.info("and we see %s ", FustionTablesNames.all().filter('table_id =', table).fetch(1)) + logging.info("and zone %s ", zone) + logging.info("and format %s ", format) - table_names = FustionTablesNames.all().filter('table_id =', table).fetch(1)[0].as_dict() + reports = [Report.get_by_id(x) for x in reports] + for r in reports: + if not r: + logging.error("report not found") + abort(404) + + stats = this_report.get_stats(r, table) - # return the stats for each zone - if not zone: - csv_file.writerow(('report_id', 'start_date', 'end_date','zone_id', 'deforested', 'degraded')) - reports = [Report.get_by_id(x) for x in reports] - for r in reports: - if not r: - logging.error("report not found") - abort(404) - st = StatsStore.get_for_report(str(r.key())) - if not st: - logging.error("no stats for report") - abort(404) - stats = st.for_table(table) - for s in stats: - name = table_names.get(s['id'], s['id']) - csv_file.writerow((str(r.key().id()), - r.start.isoformat(), - r.end.isoformat(), - name, - s['def'], - s['deg'])) - - else: - csv_file.writerow(('report_id', 'start_date', 'end_date', 'deforested', 'degraded')) - reports = [Report.get_by_id(x) for x in reports] - for r in reports: - if not r: - abort(404) - report_id = str(r.key()) - st = StatsStore.get_for_report(report_id) - - if not st: - logging.error("no cached stats for %s" % report_id) - abort(404) - - stats = st.table_accum(table, zone) - if not stats: - logging.error("no stats for %s" % report_id) - abort(404) - - csv_file.writerow((str(r.key().id()), - r.start.isoformat(), - r.end.isoformat(), - stats['def'], - stats['deg'])) - - return Response(f.getvalue(), - headers={ - "Content-Disposition": "attachment; filename=\"report_%s.csv\"" % table - }, - mimetype='text/csv') - - -@app.route('/api/v0/stats/polygon/csv') -def polygon_stats_csv(): + for s in stats: + this_report.write_row(r, s, table) + + this_report.write_footer() + return this_report.response("report_%s" % table) + + +@app.route('/api/v0/stats/polygon/') +def polygon_stats(format=None): reports = request.args.get('reports', None) if not reports: abort(400) @@ -131,30 +100,29 @@ def polygon_stats_csv(): except ValueError: logging.error("can't find some report") abort(404) + #TODO: test if polygon is ccw # exchange lat, lon -> lon, lat polygon = json.loads(request.args.get('polygon', None)) + polygon.append(polygon[0]) + logging.info(polygon) + logging.info(path_to_kml([polygon])) if not polygon: abort(404) ee = Stats() normalized_poly = [(coord[1], coord[0]) for coord in polygon] stats = ee.get_stats_for_polygon([(str(r.key().id()), r.assetid) for r in reports], [normalized_poly]) + + this_report = ReportType.factory(format) + this_report.init("custom polygon") try: - f = StringIO() - csv_file = csv.writer(f) - csv_file.writerow(('report_id', 'start_date', 'end_date', 'deforested', 'degraded')) + this_report.write_header() for i,s in enumerate(stats): r = reports[i] - csv_file.writerow((str(r.key().id()), - r.start.isoformat(), - r.end.isoformat(), - s['def'], - s['deg'])) - return Response(f.getvalue(), - headers={ - "Content-Disposition": "attachment; filename=\"polygon.csv\"" - }, - mimetype='text/csv') + this_report.write_row(r, s, None, path_to_kml([polygon])) + + this_report.write_footer() + return this_report.response("report_polygon") except (KeyError, ValueError, IndexError): abort(404) diff --git a/src/application/ft.py b/src/application/ft.py index 54b3e6b..35f7426 100755 --- a/src/application/ft.py +++ b/src/application/ft.py @@ -51,7 +51,6 @@ def create_table(self, table): def sql(self, sql): logging.debug("FT:SQL: %s" % sql) r = self.client.query(sql) - logging.debug("-> %s" % r) return r diff --git a/src/application/generate_reports.py b/src/application/generate_reports.py index 6ee51dc..bd3058b 100644 --- a/src/application/generate_reports.py +++ b/src/application/generate_reports.py @@ -7,34 +7,36 @@ import urllib2 from time_utils import month_range -assets_id = ['SAD_VALIDATED/SAD_2009_08', -'SAD_VALIDATED/SAD_2009_09', -'SAD_VALIDATED/SAD_2009_10', -'SAD_VALIDATED/SAD_2009_11', -'SAD_VALIDATED/SAD_2009_12', -'SAD_VALIDATED/SAD_2010_01', -'SAD_VALIDATED/SAD_2010_02', -'SAD_VALIDATED/SAD_2010_05', -'SAD_VALIDATED/SAD_2010_06', -'SAD_VALIDATED/SAD_2010_07', -'SAD_VALIDATED/SAD_2010_08', -'SAD_VALIDATED/SAD_2010_09', -'SAD_VALIDATED/SAD_2010_10', -'SAD_VALIDATED/SAD_2010_11', -'SAD_VALIDATED/SAD_2010_12', +assets_id = [ +#'SAD_VALIDATED/SAD_2009_08', +#'SAD_VALIDATED/SAD_2009_09', +#'SAD_VALIDATED/SAD_2009_10', +#'SAD_VALIDATED/SAD_2009_11', +#'SAD_VALIDATED/SAD_2009_12', +#'SAD_VALIDATED/SAD_2010_01', +#'SAD_VALIDATED/SAD_2010_02', +#'SAD_VALIDATED/SAD_2010_05', +#'SAD_VALIDATED/SAD_2010_06', +#'SAD_VALIDATED/SAD_2010_07', +#'SAD_VALIDATED/SAD_2010_08', +#'SAD_VALIDATED/SAD_2010_09', +#'SAD_VALIDATED/SAD_2010_10', +#'SAD_VALIDATED/SAD_2010_11', +#'SAD_VALIDATED/SAD_2010_12', 'SAD_VALIDATED/SAD_2011_01', -'SAD_VALIDATED/SAD_2011_02', -'SAD_VALIDATED/SAD_2011_03', -'SAD_VALIDATED/SAD_2011_04', -'SAD_VALIDATED/SAD_2011_05', -'SAD_VALIDATED/SAD_2011_06', -'SAD_VALIDATED/SAD_2011_07', -'SAD_VALIDATED/SAD_2011_08', -'SAD_VALIDATED/SAD_2011_09'] - +#'SAD_VALIDATED/SAD_2011_02', +#'SAD_VALIDATED/SAD_2011_03', +#'SAD_VALIDATED/SAD_2011_04', +#'SAD_VALIDATED/SAD_2011_05', +#'SAD_VALIDATED/SAD_2011_06', +#'SAD_VALIDATED/SAD_2011_07', +#'SAD_VALIDATED/SAD_2011_08', +#'SAD_VALIDATED/SAD_2011_09' +] print "assetid,report_id" -for r in assets_id[-1:]: +#for r in assets_id[-1:]: +for r in assets_id: first, last = month_range(*reversed(map(int, r.split('_')[-2:]))) url = "http://%s/_ah/cmd/create_report?year=%d&month=%d&day=%d&assetid=%s&fyear=%d&fmonth=%d&fday=%d""" % (sys.argv[1], first.year, first.month, first.day, r, last.year, last.month, last.day) print url diff --git a/src/application/models.py b/src/application/models.py index 30b5b0a..053d902 100755 --- a/src/application/models.py +++ b/src/application/models.py @@ -512,10 +512,11 @@ def table_accum(self, table, zone=None): if not table_stats: logging.info("no stats for %s on %s" % (table, self.report_id)) return None - return { + return [{ + 'id': zone, 'def': reduce(operator.add, map(float, (x['def'] for x in table_stats))), 'deg': reduce(operator.add, map(float, (x['deg'] for x in table_stats))) - } + }] class FustionTablesNames(db.Model): table_id = db.StringProperty() @@ -523,4 +524,3 @@ class FustionTablesNames(db.Model): def as_dict(self): return json.loads(self.json) - diff --git a/src/application/resources/report.py b/src/application/resources/report.py index ffec513..e6ee2fe 100755 --- a/src/application/resources/report.py +++ b/src/application/resources/report.py @@ -19,6 +19,7 @@ from google.appengine.api import memcache + class NDFIMapApi(Resource): """ resource to get ndfi map access data """ @@ -224,11 +225,6 @@ def rgb_mapid(self, report_id, id, r, g, b): return Response(json.dumps(mapid['data']), mimetype='application/json') - - - - - class PolygonAPI(Resource): def list(self, report_id, cell_pos): diff --git a/src/application/settings.py b/src/application/settings.py index 221c685..70858ea 100755 --- a/src/application/settings.py +++ b/src/application/settings.py @@ -24,8 +24,8 @@ if DEBUG: - FT_TABLE = 'areas_dev' - FT_TABLE_ID = '1556991' + FT_TABLE = 'imazon_testing' + FT_TABLE_ID = '2676501' else: app_id = app_identity.get_application_id() if app_id == 'imazon-sad-tool': diff --git a/src/content/templates/404.html b/src/content/templates/404.html index 6b8191c..d0bd85a 100755 --- a/src/content/templates/404.html +++ b/src/content/templates/404.html @@ -2,6 +2,6 @@ {% block text_box %}

404 - Not found.

-

The page have asked for does not exist. Please check the URL and try again, or go to home page ans start again

+

The page have asked for does not exist. Please check the URL and try again, or go to home page and start again

{% endblock %} diff --git a/src/index.yaml b/src/index.yaml index bc9a180..054eaaa 100644 --- a/src/index.yaml +++ b/src/index.yaml @@ -16,6 +16,11 @@ indexes: - name: added_on direction: desc +- kind: Report + properties: + - name: finished + - name: start + - kind: Report properties: - name: finished diff --git a/src/static/js/vis/report_dialog.js b/src/static/js/vis/report_dialog.js index 2148482..f56799c 100644 --- a/src/static/js/vis/report_dialog.js +++ b/src/static/js/vis/report_dialog.js @@ -37,7 +37,7 @@ var ReportDialog = Backbone.View.extend({ //input this.reports = this.options.reports; - this.formats = ['csv'];//, 'kml']; + this.formats = ['csv', 'kml']; $(document).bind('keydown', this.keyPress); @@ -119,18 +119,17 @@ var ReportDialog = Backbone.View.extend({ var url = '/api/v0/stats/'; if(this.custom) { if(this.custom.polygon) { - url += 'polygon/csv'; + url += 'polygon/' + this.selected_format; } else { - url += this.custom.table + '/' + this.custom.zone; + url += this.custom.table + '/' + this.selected_format + '/' + this.custom.zone; } } else { - url += this.region_selected.table; + url += this.region_selected.table + '/' + this.selected_format; } url += '?reports=' + reports.join(','); if(this.custom && this.custom.polygon) { url += '&polygon=' + encodeURI(JSON.stringify(this.custom.polygon)); } - //alert(url); console.log(url); window.open(url); }, diff --git a/src/static/js/vis/stats.js b/src/static/js/vis/stats.js index e312816..71e1ff8 100644 --- a/src/static/js/vis/stats.js +++ b/src/static/js/vis/stats.js @@ -9,7 +9,7 @@ var ReportStat = Backbone.Model.extend({ /* ======================================== - store all statictics in the cliend side. Take this class + store all statistics in the client side. Take this class as a memcache in client side ======================================== */ @@ -80,6 +80,11 @@ var PolygonStat = Backbone.Model.extend({ $.post(this.url() + '/csv', this.attributes ); + }, + get_kml: function() { + $.post(this.url() + '/kml', + this.attributes + ); } }); From 6e58742ee59faa26c3a753d22dfa977ae1cb8341 Mon Sep 17 00:00:00 2001 From: Dave Thau Date: Mon, 23 Jan 2012 15:33:54 -0800 Subject: [PATCH 3/3] cleaned up the code a bit --- src/application/report_types.py | 47 ++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/application/report_types.py b/src/application/report_types.py index ec72ead..8f04e7e 100755 --- a/src/application/report_types.py +++ b/src/application/report_types.py @@ -14,7 +14,7 @@ from ft import FT from flask import Response, abort, request from StringIO import StringIO -from models import FustionTablesNames, StatsStore, FusionTablesPolygons +from models import FustionTablesNames, StatsStore class ReportType(object): @@ -61,6 +61,12 @@ def get_stats(self, report, table): logging.error("no stats for %s" % report_id) abort(404) return stats + + def get_polygon_name(table, id): + all_table_name = FustionTablesNames.all() + filtered_table_names = all_table_names.filter('table_id =', table) + table_names=filtered_table_names.fetch(1)[0].as_dict() + name = table_names.get(id, id) @staticmethod def factory(format): @@ -78,9 +84,11 @@ class CSVReportType(ReportType): def write_header(self): if self.zone: - self.csv_file.writerow(('report_id', 'start_date', 'end_date', 'deforested', 'degraded')) + self.csv_file.writerow(('report_id', 'start_date', 'end_date', + 'deforested', 'degraded')) else: - self.csv_file.writerow(('report_id', 'start_date', 'end_date', 'zone_id', 'deforested', 'degraded')) + self.csv_file.writerow(('report_id', 'start_date', 'end_date', + 'zone_id', 'deforested', 'degraded')) def write_footer(self): pass @@ -89,8 +97,7 @@ def write_row(self, report, stats, table=None, kml=None): name = None if table and not self.zone: - table_names = FustionTablesNames.all().filter('table_id =', table).fetch(1)[0].as_dict() - name = table_names.get(stats['id'], stats['id']) + name = get_polygon_name(table, stats['id']) if name: self.csv_file.writerow((str(report.key().id()), @@ -115,7 +122,8 @@ def response(self, file_name): self.f.truncate(0) return Response(result, headers={ - "Content-Disposition": "attachment; filename=\"" + file_name + ".csv\"" + "Content-Disposition": "attachment; filename=\"" + file_name + + ".csv\"" }, mimetype='text/csv') @@ -127,8 +135,11 @@ def write_header(self): self.f.write("") self.f.write("") self.f.write("") - self.f.write("") + self.f.write("") def write_footer(self): self.f.write("") @@ -138,11 +149,10 @@ def write_row(self, report, stats, table=None, kml=None): name = None if table: - table_names = FustionTablesNames.all().filter('table_id =', table).fetch(1)[0].as_dict() - name = table_names.get(stats['id'], stats['id']) - kml = self.kml(table, stats['id']) + name = get_polygon_name(table, stats['id']) + kml = self.kml(table, stats['id']) else: - name = "Custom Polygon" + name = "Custom Polygon" description = self.description(name, stats) @@ -161,7 +171,8 @@ def response(self, file_name): self.f.truncate(0) return Response(result, headers={ - "Content-Disposition": "attachment; filename=\"" + file_name + ".kml\"" + "Content-Disposition": "attachment; filename=\"" + file_name + + ".kml\"" }, mimetype='text/kml') @@ -177,13 +188,19 @@ def kml(self, table, row_id): else: id = 'name' - info = cl.sql("select geometry from %s where %s = %s" % (table, id, row_id)) + info = cl.sql("select geometry from %s where %s = %s" % + (table, id, row_id)) polygon = info.split('\n')[1] polygon = polygon.replace("\"", "") return polygon def description(self, name, stats): - desc = "

" + name + "

Deforestation: " + str(stats['def']) + "km2
Degradation: " + str(stats['deg']) + "km2
]]>" + desc = "

" + name + desc += "

" + desc += "Deforestation: " + desc += str(stats['def']) + "km2" + desc += "Degradation: " + str(stats['deg']) + desc += "km2]]>" return desc