From b91ba9027858c928e7845ffb02f64330dccf546f Mon Sep 17 00:00:00 2001 From: t-ketchem <41091750+t-ketchem@users.noreply.github.com> Date: Thu, 3 Oct 2019 20:42:20 -0500 Subject: [PATCH 1/5] creates locationMap endpoint which returns LocationMarkers for all of the locations. Need verification that these are in the right format --- api/src/resources/locations.py | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/api/src/resources/locations.py b/api/src/resources/locations.py index 99888f6..59cbde3 100644 --- a/api/src/resources/locations.py +++ b/api/src/resources/locations.py @@ -76,3 +76,38 @@ def get(self, id): api.add_resource(LocationProjectAPI, '/locations//project', endpoint='location_project') + + +class LocationMapComponent(BaseListAPI): + base = LocationAPI + + def get(self): + locations = Location.query.all() + data = [] + for location in locations: + # Get lat long info for the location + coords = location.coords + # This to be populated later for polygons and other shapes + LatLongArray = [] + LatLongCenter = {'lat': coords.get('latitude'), 'long': coords.get('longitude')} + + # Do a query to get more info about the project using the project id + # TODO: Find a better way to do these queries + thisProject = Project.query.filter_by(id = location.project_id).all() + thisProject = thisProject[0] # take the one entry out of the list + + # find the project type: + thisProjectType = ProjectType.query.filter_by(id = thisProject.project_type_id).all() + thisProjectType = thisProjectType[0] # take the one entry out of the list + + # Compile the information into format needed + data.append({ + 'points': LatLongArray, + 'project_name': thisProject.name, #this needs to be project name... + 'center': LatLongCenter, + 'project_id': location.project_id, + 'project_type': thisProjectType.type_name #need to look this up + }) + return jsonify({'LocationMarkers': data}) + +api.add_resource(LocationMapComponent, '/locationMap', '/locationMap/') From a63c14506fdb2732f015dea0e86bdda9b230df4b Mon Sep 17 00:00:00 2001 From: t-ketchem <41091750+t-ketchem@users.noreply.github.com> Date: Mon, 7 Oct 2019 18:54:53 -0500 Subject: [PATCH 2/5] changes to clean up query --- api/src/resources/locations.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/api/src/resources/locations.py b/api/src/resources/locations.py index 59cbde3..d884197 100644 --- a/api/src/resources/locations.py +++ b/api/src/resources/locations.py @@ -92,13 +92,10 @@ def get(self): LatLongCenter = {'lat': coords.get('latitude'), 'long': coords.get('longitude')} # Do a query to get more info about the project using the project id - # TODO: Find a better way to do these queries - thisProject = Project.query.filter_by(id = location.project_id).all() - thisProject = thisProject[0] # take the one entry out of the list + thisProject = Project.query.filter_by(id = location.project_id).first() # find the project type: - thisProjectType = ProjectType.query.filter_by(id = thisProject.project_type_id).all() - thisProjectType = thisProjectType[0] # take the one entry out of the list + thisProjectType = ProjectType.query.filter_by(id = thisProject.project_type_id).first() # Compile the information into format needed data.append({ From f0726e00be8472209bbda67cd44c6fbdc44ea219 Mon Sep 17 00:00:00 2001 From: t-ketchem <41091750+t-ketchem@users.noreply.github.com> Date: Mon, 7 Oct 2019 19:01:28 -0500 Subject: [PATCH 3/5] cleanup --- api/src/resources/locations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/resources/locations.py b/api/src/resources/locations.py index d884197..eafbc49 100644 --- a/api/src/resources/locations.py +++ b/api/src/resources/locations.py @@ -100,10 +100,10 @@ def get(self): # Compile the information into format needed data.append({ 'points': LatLongArray, - 'project_name': thisProject.name, #this needs to be project name... + 'project_name': thisProject.name, 'center': LatLongCenter, 'project_id': location.project_id, - 'project_type': thisProjectType.type_name #need to look this up + 'project_type': thisProjectType.type_name }) return jsonify({'LocationMarkers': data}) From b99e16994ad01995a2be9593b3f2d6717ad49bc2 Mon Sep 17 00:00:00 2001 From: t-ketchem <41091750+t-ketchem@users.noreply.github.com> Date: Mon, 7 Oct 2019 19:16:19 -0500 Subject: [PATCH 4/5] remove redundant tag --- api/src/resources/locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/resources/locations.py b/api/src/resources/locations.py index eafbc49..37a571e 100644 --- a/api/src/resources/locations.py +++ b/api/src/resources/locations.py @@ -105,6 +105,6 @@ def get(self): 'project_id': location.project_id, 'project_type': thisProjectType.type_name }) - return jsonify({'LocationMarkers': data}) + return jsonify(data) api.add_resource(LocationMapComponent, '/locationMap', '/locationMap/') From e62c979797bff325625500133ef9457eee25fef9 Mon Sep 17 00:00:00 2001 From: Wes Galbraith Date: Mon, 4 Nov 2019 23:21:24 -0600 Subject: [PATCH 5/5] aggregated project fields over multiple years --- api/src/resources/locations.py | 135 +++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 24 deletions(-) diff --git a/api/src/resources/locations.py b/api/src/resources/locations.py index 37a571e..c58545d 100644 --- a/api/src/resources/locations.py +++ b/api/src/resources/locations.py @@ -1,6 +1,10 @@ +from functools import partial, reduce +from collections import defaultdict, namedtuple + from flask import Blueprint, request, jsonify from flask_restful import Api, Resource, fields from geoalchemy2 import functions +from sqlalchemy import func from .base import BaseAPI, BaseListAPI from models import db, Project, ProjectType, Location @@ -78,33 +82,116 @@ def get(self, id): api.add_resource(LocationProjectAPI, '/locations//project', endpoint='location_project') -class LocationMapComponent(BaseListAPI): - base = LocationAPI +def build_project_year_query(min_year, max_year): + min_year_query = (Project.year >= min_year) if min_year else None + max_year_query = (Project.year <= max_year) if max_year else None - def get(self): - locations = Location.query.all() - data = [] - for location in locations: - # Get lat long info for the location - coords = location.coords - # This to be populated later for polygons and other shapes - LatLongArray = [] - LatLongCenter = {'lat': coords.get('latitude'), 'long': coords.get('longitude')} + if min_year is not None and max_year is not None: + year_query = min_year_query & max_year_query + elif max_year is not None: + year_query = max_year_query + elif min_year is not None: + year_query = min_year_query + else: + year_query = None - # Do a query to get more info about the project using the project id - thisProject = Project.query.filter_by(id = location.project_id).first() + return year_query - # find the project type: - thisProjectType = ProjectType.query.filter_by(id = thisProject.project_type_id).first() - # Compile the information into format needed - data.append({ - 'points': LatLongArray, - 'project_name': thisProject.name, - 'center': LatLongCenter, - 'project_id': location.project_id, - 'project_type': thisProjectType.type_name - }) - return jsonify(data) +def format_map_component_output(location, project): + return { + 'points': LatLongArray, + 'project_name': thisProject.name, + 'center': LatLongCenter, + 'project_id': location.project_id, + 'project_type': thisProjectType.type_name + } + +def query_projects(min_year, max_year, project_types): + if project_types is None: + project_types = [] + project_types = list(map(lambda type: type.lower(), project_types)) + project_types = ProjectType.query.filter( + func.lower(ProjectType.type_name).in_(project_types)).all() + project_type_ids = [type.id for type in project_types] + + year_query = build_project_year_query(min_year, max_year) + + project_type_query = (Project.project_type_id.in_(project_type_ids)) + + projects = Project.query + if year_query is not None: + projects = projects.filter(year_query) + + projects = projects.filter(project_type_query) + + projects = projects.all() + + return projects + + +def compute_centroid(points): + points = list(points) + if len(points) == 0: + return points + try: + lat = sum(p.get('lat') for p in points if p.get('lat') is not None) + lat /= len([p for p in points if p.get('lat') is not None]) + lng = sum(p.get('lng') for p in points if p.get('lng') is not None) + lng /= len([p for p in points if p.get('lng') is not None]) + return {'lat': lat, 'lng': lng} + except ZeroDivisionError as e: + return points[0] + +Coord = namedtuple('Coord', ['lat', 'lng']) + +def aggregate_projects(projects): + grouped_by_name = defaultdict(list) + for project in projects: + grouped_by_name[project.name.lower()].append(project) + aggregates = [] + for project_name, group in grouped_by_name.items(): + locations = [project.locations for project in group] + coords = [[loc.coords for loc in loc_group] for loc_group in locations] + coords_nt = [list(map(lambda c: Coord(c.get('latitude'), c.get('longitude')), coord_group)) for coord_group in coords] + points_nt = reduce( + lambda agg, next: agg | set(next), + coords_nt, + set([]) + ) + points = [p._asdict() for p in points_nt] + center = compute_centroid(points) + project_ids = [project.id for project in group] + project_types = list(set(project.type.type_name.lower() for project in group)) + gge_reduced = sum(proj.gge_reduced for proj in group if proj.gge_reduced is not None) + ghg_reduced = sum(proj.ghg_reduced for proj in group if proj.ghg_reduced is not None) + aggregates.append({ + 'points': points, + 'project_name': project_name, + 'center': center, + 'project_ids': project_ids, + 'project_types': project_types, + 'ghg_reduced': ghg_reduced, + 'gge_reduced': gge_reduced + }) + return aggregates + + +class LocationMapComponent(BaseListAPI): + base = LocationAPI() + + def get(self): + min_year = request.args.get('minYear') + if min_year is not None: + min_year = int(min_year) + max_year = request.args.get('maxYear') + if max_year is not None: + max_year = int(max_year) + project_types = request.args.getlist('projectType') + # format project types + projects = query_projects(min_year, max_year, project_types) + projects_agg = aggregate_projects(projects) + return jsonify(projects_agg) + api.add_resource(LocationMapComponent, '/locationMap', '/locationMap/')