Commit 2ac05e0c5c098c1b6b9a39fef146a466c03f074b

Authored by Antoine Goutenoir
1 parent bd79940b
Exists in master

feat: rely on fully qualified addresses

and continue moving to python3

/spend 8h
flaskr/controllers/main_controller.py
@@ -37,7 +37,7 @@ from flaskr.models import db, Estimation, StatusEnum, ScenarioEnum @@ -37,7 +37,7 @@ from flaskr.models import db, Estimation, StatusEnum, ScenarioEnum
37 main = Blueprint('main', __name__) 37 main = Blueprint('main', __name__)
38 38
39 39
40 -OUT_ENCODING = 'utf-8' 40 +#OUT_ENCODING = 'utf-8'
41 41
42 42
43 # ----------------------------------------------------------------------------- 43 # -----------------------------------------------------------------------------
@@ -489,6 +489,13 @@ def compute(): # process the queue of estimation requests @@ -489,6 +489,13 @@ def compute(): # process the queue of estimation requests
489 489
490 # UTILITY PRIVATE FUNCTION(S) ######################################### 490 # UTILITY PRIVATE FUNCTION(S) #########################################
491 491
  492 + # _locations
  493 + def _get_location_key(_location):
  494 + return "%s, %s" % (
  495 + _get_city_key(_location),
  496 + _get_country_key(_location),
  497 + )
  498 +
492 def _get_city_key(_location): 499 def _get_city_key(_location):
493 return _location.address.split(',')[0] 500 return _location.address.split(',')[0]
494 501
@@ -512,7 +519,7 @@ def compute(): # process the queue of estimation requests @@ -512,7 +519,7 @@ def compute(): # process the queue of estimation requests
512 _results = {} 519 _results = {}
513 footprints = {} 520 footprints = {}
514 521
515 - destinations_by_city_key = {} 522 + destinations_by_key = {}
516 523
517 cities_sum_foot = {} 524 cities_sum_foot = {}
518 cities_sum_dist = {} 525 cities_sum_dist = {}
@@ -528,13 +535,13 @@ def compute(): # process the queue of estimation requests @@ -528,13 +535,13 @@ def compute(): # process the queue of estimation requests
528 extra_config=_extra_config, 535 extra_config=_extra_config,
529 ) 536 )
530 537
531 - _key = _get_city_key(_destination)  
532 -  
533 - destinations_by_city_key[_key] = _destination 538 + _key = _get_location_key(_destination)
  539 + destinations_by_key[_key] = _destination
534 540
535 if _key not in cities_dict: 541 if _key not in cities_dict:
536 cities_dict[_key] = { 542 cities_dict[_key] = {
537 - 'city': _key, 543 + 'location': _get_location_key(_destination),
  544 + 'city': _get_city_key(_destination),
538 'country': _get_country_key(_destination), 545 'country': _get_country_key(_destination),
539 'address': _destination.address, 546 'address': _destination.address,
540 'latitude': _destination.latitude, 547 'latitude': _destination.latitude,
@@ -578,11 +585,12 @@ def compute(): # process the queue of estimation requests @@ -578,11 +585,12 @@ def compute(): # process the queue of estimation requests
578 city_train_trips = cities_dict_first_model[city]['train_trips'] 585 city_train_trips = cities_dict_first_model[city]['train_trips']
579 city_plane_trips = cities_dict_first_model[city]['plane_trips'] 586 city_plane_trips = cities_dict_first_model[city]['plane_trips']
580 cities_mean_dict[city] = { 587 cities_mean_dict[city] = {
581 - 'city': city,  
582 - 'country': _get_country_key(destinations_by_city_key[city]),  
583 - 'address': destinations_by_city_key[city].address,  
584 - 'latitude': destinations_by_city_key[city].latitude,  
585 - 'longitude': destinations_by_city_key[city].longitude, 588 + 'location': _get_location_key(destinations_by_key[city]),
  589 + 'city': _get_city_key(destinations_by_key[city]),
  590 + 'country': _get_country_key(destinations_by_key[city]),
  591 + 'address': destinations_by_key[city].address,
  592 + 'latitude': destinations_by_key[city].latitude,
  593 + 'longitude': destinations_by_key[city].longitude,
586 'footprint': city_mean_foot, 594 'footprint': city_mean_foot,
587 'distance': city_mean_dist, 595 'distance': city_mean_dist,
588 'train_trips': city_train_trips, 596 'train_trips': city_train_trips,
@@ -638,19 +646,21 @@ def compute(): # process the queue of estimation requests @@ -638,19 +646,21 @@ def compute(): # process the queue of estimation requests
638 # SCENARIO C : At Least One Origin, At Least One Destination ########## 646 # SCENARIO C : At Least One Origin, At Least One Destination ##########
639 # 647 #
640 # Run Scenario A for each Destination, and expose optimum Destination. 648 # Run Scenario A for each Destination, and expose optimum Destination.
  649 + # Skip destinations already visited. (collapse duplicate destinations)
641 # 650 #
642 else: 651 else:
643 estimation.scenario = ScenarioEnum.many_to_many 652 estimation.scenario = ScenarioEnum.many_to_many
644 - unique_city_keys = [] 653 + unique_location_keys = []
645 result_cities = [] 654 result_cities = []
646 for destination in destinations: 655 for destination in destinations:
  656 + location_key = _get_location_key(destination)
647 city_key = _get_city_key(destination) 657 city_key = _get_city_key(destination)
648 country_key = _get_country_key(destination) 658 country_key = _get_country_key(destination)
649 659
650 - if city_key in unique_city_keys: 660 + if location_key in unique_location_keys:
651 continue 661 continue
652 else: 662 else:
653 - unique_city_keys.append(city_key) 663 + unique_location_keys.append(location_key)
654 664
655 city_results = compute_one_to_many( 665 city_results = compute_one_to_many(
656 _origin=destination, 666 _origin=destination,
@@ -659,6 +669,7 @@ def compute(): # process the queue of estimation requests @@ -659,6 +669,7 @@ def compute(): # process the queue of estimation requests
659 ) 669 )
660 city_results['city'] = city_key 670 city_results['city'] = city_key
661 city_results['country'] = country_key 671 city_results['country'] = country_key
  672 + city_results['location'] = location_key
662 city_results['address'] = destination.address 673 city_results['address'] = destination.address
663 city_results['latitude'] = destination.latitude 674 city_results['latitude'] = destination.latitude
664 city_results['longitude'] = destination.longitude 675 city_results['longitude'] = destination.longitude
@@ -719,7 +730,7 @@ def consult_estimation(public_id, extension): @@ -719,7 +730,7 @@ def consult_estimation(public_id, extension):
719 except sqlalchemy.orm.exc.NoResultFound: 730 except sqlalchemy.orm.exc.NoResultFound:
720 return abort(404) 731 return abort(404)
721 except Exception as e: 732 except Exception as e:
722 - # TODO: log? 733 + # log? (or not)
723 return abort(500) 734 return abort(500)
724 735
725 # allowed_formats = ['html'] 736 # allowed_formats = ['html']
@@ -763,6 +774,7 @@ def consult_estimation(public_id, extension): @@ -763,6 +774,7 @@ def consult_estimation(public_id, extension):
763 si = StringIO() 774 si = StringIO()
764 cw = csv.writer(si, quoting=csv.QUOTE_ALL) 775 cw = csv.writer(si, quoting=csv.QUOTE_ALL)
765 cw.writerow([ 776 cw.writerow([
  777 + u"location",
766 u"city", u"country", u"address", 778 u"city", u"country", u"address",
767 u"latitude", u"longitude", 779 u"latitude", u"longitude",
768 u"co2_kg", 780 u"co2_kg",
@@ -774,9 +786,11 @@ def consult_estimation(public_id, extension): @@ -774,9 +786,11 @@ def consult_estimation(public_id, extension):
774 results = estimation.get_output_dict() 786 results = estimation.get_output_dict()
775 for city in results['cities']: 787 for city in results['cities']:
776 cw.writerow([ 788 cw.writerow([
777 - city['city'].encode(OUT_ENCODING),  
778 - city['country'].encode(OUT_ENCODING),  
779 - city['address'].encode(OUT_ENCODING), 789 + # city['location'].encode(OUT_ENCODING),
  790 + city['location'],
  791 + city['city'],
  792 + city['country'],
  793 + city['address'],
780 city.get('latitude', 0.0), 794 city.get('latitude', 0.0),
781 city.get('longitude', 0.0), 795 city.get('longitude', 0.0),
782 round(city['footprint'], 3), 796 round(city['footprint'], 3),
@@ -950,6 +964,51 @@ def get_scaling_laws_csv(): @@ -950,6 +964,51 @@ def get_scaling_laws_csv():
950 ) 964 )
951 965
952 966
  967 +@main.route('/geocode')
  968 +@main.route('/geocode.html')
  969 +def query_geocode():
  970 + requested = request.args.getlist('address')
  971 + if not requested:
  972 + requested = request.args.getlist('address[]')
  973 + if not requested:
  974 + requested = request.args.getlist('a')
  975 + if not requested:
  976 + requested = request.args.getlist('a[]')
  977 + #requested = _collect_request_args_list(('address', 'a'))
  978 + if not requested:
  979 + return Response(
  980 + response="""
  981 +<p>
  982 +Usage example: <a href="/geocode.html?address=Toulouse,France&amp;address=Paris,France">/geocode?address=Toulouse,France</a>
  983 +</p>
  984 +
  985 +<p>
  986 +Please do not request this endpoint more than every two seconds.
  987 +</p>
  988 +
  989 +
  990 +"""
  991 + )
  992 +
  993 + response = u""
  994 +
  995 + geocoder = CachedGeocoder()
  996 + for address in requested:
  997 + location = geocoder.geocode(address)
  998 +
  999 + response += """
  1000 +<pre>
  1001 +Requested: `%s'
  1002 +Geocoded: `%s'
  1003 +Longitude: `%f`
  1004 +Latitude: `%f`
  1005 +Altitude: `%f` (unreliable)
  1006 +</pre>
  1007 +""" % (address, location, location.longitude, location.latitude, location.altitude)
  1008 +
  1009 + return Response(response=response)
  1010 +
  1011 +
953 @main.route("/test") 1012 @main.route("/test")
954 # @basic_auth.required 1013 # @basic_auth.required
955 def dev_test(): 1014 def dev_test():
flaskr/templates/estimation.html
@@ -378,13 +378,12 @@ jQuery(document).ready(function($){ @@ -378,13 +378,12 @@ jQuery(document).ready(function($){
378 console.info("[Footprint Lollipop] Starting…"); 378 console.info("[Footprint Lollipop] Starting…");
379 var vizid = "#cities_footprints_d3viz_lollipop"; 379 var vizid = "#cities_footprints_d3viz_lollipop";
380 var csvUrl = "/estimation/{{ estimation.public_id }}.csv"; 380 var csvUrl = "/estimation/{{ estimation.public_id }}.csv";
381 - var y_key = 'city'; 381 + var y_key = 'address';
382 var x_key = 'co2_kg'; 382 var x_key = 'co2_kg';
383 383
384 - var margin = {top: 40, right: 40, bottom: 150, left: 180},  
385 - height = Math.max(300, 100 + 16*plots_config['cities_count']) - margin.top - margin.bottom;  
386 - var width = Math.max(800, $(vizid).parent().width());  
387 - width = width - margin.left - margin.right; 384 + var margin = {top: 40, right: 40, bottom: 150, left: 180};
  385 + var height = Math.max(300, 100 + 16*plots_config['cities_count']) - margin.top - margin.bottom;
  386 + var width = Math.max(800, $(vizid).parent().width()) - margin.left - margin.right;
388 387
389 var svg_tag = d3.select(vizid) 388 var svg_tag = d3.select(vizid)
390 .append("svg") 389 .append("svg")
@@ -404,6 +403,20 @@ jQuery(document).ready(function($){ @@ -404,6 +403,20 @@ jQuery(document).ready(function($){
404 403
405 d3.csv(csvUrl).then(function (data) { 404 d3.csv(csvUrl).then(function (data) {
406 console.info("[Footprint Lollipop] Generating…"); 405 console.info("[Footprint Lollipop] Generating…");
  406 +
  407 + // Resize left margin from locations' character length
  408 + var max_character_length = 0;
  409 + data.forEach(function(datum){
  410 + max_character_length = Math.max(max_character_length, datum[y_key].length);
  411 + });
  412 + margin.left = Math.min(Math.round(0.618 * width), 42 + Math.round(5.13*max_character_length));
  413 + width = Math.max(800, $(vizid).parent().width()) - margin.left - margin.right;
  414 + svg_tag
  415 + .attr("width", width + margin.left + margin.right)
  416 + .attr("height", height + margin.top + margin.bottom);
  417 + svg.attr("transform",
  418 + "translate(" + margin.left + "," + margin.top + ")");
  419 +
407 // Extrema 420 // Extrema
408 var data_x_max = d3.max(data, function (d) { 421 var data_x_max = d3.max(data, function (d) {
409 return parseFloat(d[x_key]); 422 return parseFloat(d[x_key]);