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 | 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 | 489 | |
490 | 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 | 499 | def _get_city_key(_location): |
493 | 500 | return _location.address.split(',')[0] |
494 | 501 | |
... | ... | @@ -512,7 +519,7 @@ def compute(): # process the queue of estimation requests |
512 | 519 | _results = {} |
513 | 520 | footprints = {} |
514 | 521 | |
515 | - destinations_by_city_key = {} | |
522 | + destinations_by_key = {} | |
516 | 523 | |
517 | 524 | cities_sum_foot = {} |
518 | 525 | cities_sum_dist = {} |
... | ... | @@ -528,13 +535,13 @@ def compute(): # process the queue of estimation requests |
528 | 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 | 541 | if _key not in cities_dict: |
536 | 542 | cities_dict[_key] = { |
537 | - 'city': _key, | |
543 | + 'location': _get_location_key(_destination), | |
544 | + 'city': _get_city_key(_destination), | |
538 | 545 | 'country': _get_country_key(_destination), |
539 | 546 | 'address': _destination.address, |
540 | 547 | 'latitude': _destination.latitude, |
... | ... | @@ -578,11 +585,12 @@ def compute(): # process the queue of estimation requests |
578 | 585 | city_train_trips = cities_dict_first_model[city]['train_trips'] |
579 | 586 | city_plane_trips = cities_dict_first_model[city]['plane_trips'] |
580 | 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 | 594 | 'footprint': city_mean_foot, |
587 | 595 | 'distance': city_mean_dist, |
588 | 596 | 'train_trips': city_train_trips, |
... | ... | @@ -638,19 +646,21 @@ def compute(): # process the queue of estimation requests |
638 | 646 | # SCENARIO C : At Least One Origin, At Least One Destination ########## |
639 | 647 | # |
640 | 648 | # Run Scenario A for each Destination, and expose optimum Destination. |
649 | + # Skip destinations already visited. (collapse duplicate destinations) | |
641 | 650 | # |
642 | 651 | else: |
643 | 652 | estimation.scenario = ScenarioEnum.many_to_many |
644 | - unique_city_keys = [] | |
653 | + unique_location_keys = [] | |
645 | 654 | result_cities = [] |
646 | 655 | for destination in destinations: |
656 | + location_key = _get_location_key(destination) | |
647 | 657 | city_key = _get_city_key(destination) |
648 | 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 | 661 | continue |
652 | 662 | else: |
653 | - unique_city_keys.append(city_key) | |
663 | + unique_location_keys.append(location_key) | |
654 | 664 | |
655 | 665 | city_results = compute_one_to_many( |
656 | 666 | _origin=destination, |
... | ... | @@ -659,6 +669,7 @@ def compute(): # process the queue of estimation requests |
659 | 669 | ) |
660 | 670 | city_results['city'] = city_key |
661 | 671 | city_results['country'] = country_key |
672 | + city_results['location'] = location_key | |
662 | 673 | city_results['address'] = destination.address |
663 | 674 | city_results['latitude'] = destination.latitude |
664 | 675 | city_results['longitude'] = destination.longitude |
... | ... | @@ -719,7 +730,7 @@ def consult_estimation(public_id, extension): |
719 | 730 | except sqlalchemy.orm.exc.NoResultFound: |
720 | 731 | return abort(404) |
721 | 732 | except Exception as e: |
722 | - # TODO: log? | |
733 | + # log? (or not) | |
723 | 734 | return abort(500) |
724 | 735 | |
725 | 736 | # allowed_formats = ['html'] |
... | ... | @@ -763,6 +774,7 @@ def consult_estimation(public_id, extension): |
763 | 774 | si = StringIO() |
764 | 775 | cw = csv.writer(si, quoting=csv.QUOTE_ALL) |
765 | 776 | cw.writerow([ |
777 | + u"location", | |
766 | 778 | u"city", u"country", u"address", |
767 | 779 | u"latitude", u"longitude", |
768 | 780 | u"co2_kg", |
... | ... | @@ -774,9 +786,11 @@ def consult_estimation(public_id, extension): |
774 | 786 | results = estimation.get_output_dict() |
775 | 787 | for city in results['cities']: |
776 | 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 | 794 | city.get('latitude', 0.0), |
781 | 795 | city.get('longitude', 0.0), |
782 | 796 | round(city['footprint'], 3), |
... | ... | @@ -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 | 1012 | @main.route("/test") |
954 | 1013 | # @basic_auth.required |
955 | 1014 | def dev_test(): | ... | ... |
flaskr/templates/estimation.html
... | ... | @@ -378,13 +378,12 @@ jQuery(document).ready(function($){ |
378 | 378 | console.info("[Footprint Lollipop] Starting…"); |
379 | 379 | var vizid = "#cities_footprints_d3viz_lollipop"; |
380 | 380 | var csvUrl = "/estimation/{{ estimation.public_id }}.csv"; |
381 | - var y_key = 'city'; | |
381 | + var y_key = 'address'; | |
382 | 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 | 388 | var svg_tag = d3.select(vizid) |
390 | 389 | .append("svg") |
... | ... | @@ -404,6 +403,20 @@ jQuery(document).ready(function($){ |
404 | 403 | |
405 | 404 | d3.csv(csvUrl).then(function (data) { |
406 | 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 | 420 | // Extrema |
408 | 421 | var data_x_max = d3.max(data, function (d) { |
409 | 422 | return parseFloat(d[x_key]); | ... | ... |