Commit ebfb50f03c37a598ebc99612089787624bdd993b
1 parent
c098cd65
Exists in
master
feat: rely on fully qualified addresses
and continue moving to python3 /spend 8h
Showing
2 changed files
with
95 additions
and
23 deletions
Show diff stats
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&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]); |