def get_choropleth_places(geoDcid): """ Get the list of places to show on a choropleth chart for a given place Args: geoDcid: dcid of the place of interest Returns: (list of dcids, property to use for fetching geo json) """ place_list = [] place_type = place_api.get_place_type(geoDcid) display_level = None if place_type in CHOROPLETH_DISPLAY_LEVEL_MAP: display_level = CHOROPLETH_DISPLAY_LEVEL_MAP[place_type] elif place_type in EQUIVALENT_PLACE_TYPES and EQUIVALENT_PLACE_TYPES[ place_type] in CHOROPLETH_DISPLAY_LEVEL_MAP: place_type = EQUIVALENT_PLACE_TYPES[place_type] display_level = CHOROPLETH_DISPLAY_LEVEL_MAP[place_type] else: return place_list if place_type == display_level: parents_places = place_api.parent_places(geoDcid) for parent in parents_places.get(geoDcid, []): parent_dcid = parent.get('dcid', None) if not parent_dcid: continue parent_place_types = parent.get('types', []) for parent_place_type in parent_place_types: parent_display_level = CHOROPLETH_DISPLAY_LEVEL_MAP.get( parent_place_type, None) if not parent_display_level: parent_display_level = CHOROPLETH_DISPLAY_LEVEL_MAP.get( EQUIVALENT_PLACE_TYPES.get(parent_place_type, '')) if parent_display_level == display_level: place_list = dc_service.get_places_in([parent_dcid], display_level).get( parent_dcid, []) geo_prop = CHOROPLETH_GEOJSON_PROPERTY_MAP[display_level] # Puerto Rico (geoId/72) requires higher resolution geoJson if parent_dcid == 'geoId/72': geo_prop = 'geoJsonCoordinatesDP1' return place_list, geo_prop return place_list else: place_list = dc_service.get_places_in([geoDcid], display_level).get(geoDcid, []) geo_prop = CHOROPLETH_GEOJSON_PROPERTY_MAP[display_level] # Puerto Rico (geoId/72) requires higher resolution geoJson if geoDcid == 'geoId/72': geo_prop = 'geoJsonCoordinatesDP1' return place_list, geo_prop
def child_statvars(): """ Gets all statistical variables available for a particular sublevel of a dcid. API Params: dcid: The place dcid to pull information for, as a string. level: The level of children to pull for, as a string. If omitted, then the subgeo is computed. Example Query: api/place/child/statvars?dcid=country/USA&level=State Returns all statistical variables that are value for states of the USA. Returns: A json list of all the available statistical variables. """ # Get required params. dcid = flask.request.args.get("dcid") if not dcid: return flask.jsonify({"error": "Must provide a 'dcid' field!"}, 400) requested_level = get_sublevel(dcid, flask.request.args.get("level")) # Get sublevels. geos_contained_in_place = dc.get_places_in([dcid], requested_level) if dcid not in geos_contained_in_place: return flask.jsonify({"error": "Internal server error."}, 500) geos_contained_in_place = geos_contained_in_place[dcid] # Get all available Statistical Variables for subgeos. # Only the union of the first 10 geos are returned for speed. # TODO(iancostello): Determine whether this heuristic is too generous # or too restrictive. stat_vars_for_subgeo = set() for geoId in geos_contained_in_place[:10]: stat_vars_for_subgeo = stat_vars_for_subgeo.union( place.statsvars(geoId)) return json.dumps(list(stat_vars_for_subgeo))
def geojson(): """ Get geoJson data for places enclosed within the given dcid """ place_dcid = request.args.get("placeDcid") if not place_dcid: return Response(json.dumps("error: must provide a placeDcid field"), 400, mimetype='application/json') place_type = request.args.get("placeType") if not place_type: place_dcid, place_type = get_choropleth_display_level(place_dcid) geos = [] if place_dcid and place_type: geos = dc_service.get_places_in([place_dcid], place_type).get(place_dcid, []) if not geos: return Response(json.dumps({}), 200, mimetype='application/json') geojson_prop = CHOROPLETH_GEOJSON_PROPERTY_MAP.get(place_type, "") # geoId/72 needs higher resolution geojson because otherwise, the map looks # too fragmented if place_dcid == 'geoId/72': geojson_prop = 'geoJsonCoordinatesDP1' names_by_geo = place_api.get_display_name('^'.join(geos), g.locale) geojson_by_geo = dc_service.get_property_values(geos, geojson_prop) features = [] for geo_id, json_text in geojson_by_geo.items(): if json_text and geo_id in names_by_geo: geo_feature = { "type": "Feature", "geometry": { "type": "MultiPolygon", }, "id": geo_id, "properties": { "name": names_by_geo.get(geo_id, "Unnamed Area"), "geoDcid": geo_id, } } # Load, simplify, and add geoJSON coordinates. # Exclude geo if no or multiple renderings are present. if len(json_text) != 1: continue geojson = json.loads(json_text[0]) geo_feature['geometry']['coordinates'] = ( coerce_geojson_to_righthand_rule(geojson['coordinates'], geojson['type'])) features.append(geo_feature) return Response(json.dumps({ "type": "FeatureCollection", "features": features, "properties": { "current_geo": place_dcid } }), 200, mimetype='application/json')
def choropleth_values(): """Returns data for geographic subregions for a certain statistical variable. API Params: geoDcid: The currently viewed geography to render, as a string. level: The subgeographic level to pull and display information for, as a string. Choices: Country, AdministrativeLevel[1/2], City. statVar: The statistical variable to pull data about. API Returns: values: dictionary of geo to statistical variable values (as a float) for all subgeos. """ # Get required request parameters. requested_geoDcid = flask.request.args.get("geoDcid") if not requested_geoDcid: return flask.jsonify({"error": "Must provide a 'geoDcid' field!"}, 400) stat_var = flask.request.args.get("statVar") if not stat_var: return flask.jsonify({"error": "Must provide a 'statVar' field!"}, 400) display_level = get_sublevel(requested_geoDcid, flask.request.args.get("level")) if not display_level: return flask.jsonify( { "error": f"Failed to automatically resolve geographic subdivision level for" + f"{requested_geoDcid}. Please provide a 'level' field manually." }, 400) # Get all subgeos. geos_contained_in_place = dc.get_places_in([requested_geoDcid], display_level).get( requested_geoDcid, []) values_by_geo = dc.get_stats(geos_contained_in_place, stat_var) # Add to dictionary for response. populations_by_geo = {} for geo_id, payload in values_by_geo.items(): if payload and "data" in payload: latest_data = get_latest_data(payload) if latest_data: populations_by_geo[geo_id] = latest_data # Return as json payload. return flask.jsonify(populations_by_geo, 200)
def get_map_points(): """ Get map point data for the given place type enclosed within the given dcid """ place_dcid = request.args.get("placeDcid") if not place_dcid: return Response(json.dumps("error: must provide a placeDcid field"), 400, mimetype='application/json') place_type = request.args.get("placeType") if not place_type: return Response(json.dumps("error: must provide a placeType field"), 400, mimetype='application/json') geos = [] geos = dc_service.get_places_in([place_dcid], place_type).get(place_dcid, []) if not geos: return Response(json.dumps({}), 200, mimetype='application/json') names_by_geo = place_api.get_display_name('^'.join(geos), g.locale) latitude_by_geo = dc_service.get_property_values(geos, "latitude") longitude_by_geo = dc_service.get_property_values(geos, "longitude") map_points_list = [] for geo_id, latitude in latitude_by_geo.items(): longitude = longitude_by_geo.get(geo_id, []) if len(latitude_by_geo) == 0 or len(longitude) == 0: continue map_point = { "placeDcid": geo_id, "placeName": names_by_geo.get(geo_id, "Unnamed Place"), "latitude": float(latitude[0]), "longitude": float(longitude[0]) } map_points_list.append(map_point) return Response(json.dumps(map_points_list), 200, mimetype='application/json')
def choropleth_geo(): """Returns data for geographic subregions for a certain statistical variable. API Params: geoDcid: The currently viewed geography to render, as a string. level: The subgeographic level to pull and display information for, as a string. Choices: Country, AdministrativeLevel[1/2], City. mdom: The measurement denominator to use as a string. Defaults to "Count_Person". API Returns: geoJson: geoJson format that includes statistical variables info, geoDcid, and name for all subregions. """ # Get required request parameters. requested_geoDcid = flask.request.args.get("geoDcid") if not requested_geoDcid: return flask.jsonify({"error": "Must provide a 'geoDcid' field!"}, 400) display_level = get_sublevel(requested_geoDcid, flask.request.args.get("level")) geo_json_prop = GEOJSON_PROPERTY_MAP.get(display_level, None) if not display_level: return flask.jsonify( { "error": f"Failed to automatically resolve geographic subdivision level for" + f"{requested_geoDcid}. Please provide a 'level' field manually." }, 400) if not geo_json_prop: return flask.jsonify( { "error": f"Geojson data is not available for the geographic subdivision" + f"level needed for {requested_geoDcid}." }, 400) # Get optional fields. measurement_denominator = flask.request.args.get("mdom", default="Count_Person") # Get list of all contained places. geos_contained_in_place = dc.get_places_in([requested_geoDcid], display_level).get( requested_geoDcid, []) # Download statistical variable, names, and geojson for subgeos. # Also, handle the case where only a fraction of values are returned. names_by_geo = dc.get_property_values( geos_contained_in_place + [requested_geoDcid], "name") geojson_by_geo = dc.get_property_values(geos_contained_in_place, geo_json_prop) # Download population data if per capita. # TODO(iancostello): Determine how to handle populations # and statistical values from different times. population_by_geo = dc.get_stats(geos_contained_in_place, measurement_denominator) # Process into a combined json object. features = [] for geo_id, json_text in geojson_by_geo.items(): # Valid response needs at least geometry and a name. if not json_text: continue if geo_id not in names_by_geo: continue geo_feature = { "type": "Feature", "geometry": { "type": "MultiPolygon", }, "id": geo_id, "properties": { # Choose the first name when multiple are present. "name": names_by_geo.get(geo_id, ["Unnamed Area"])[0], "hasSublevel": (display_level in LEVEL_MAP), "geoDcid": geo_id, } } # Load, simplify, and add geoJSON coordinates. # Exclude geo if no or multiple renderings are present. if len(json_text) != 1: continue geojson = json.loads(json_text[0]) geo_feature['geometry']['coordinates'] = ( coerce_geojson_to_righthand_rule(geojson['coordinates'], geojson['type'])) if geo_id not in population_by_geo: continue population_payload = population_by_geo[geo_id] data = get_data(population_payload) # TODO(edumorales): return the population # for N date that all places have in common. if data: max_date = max(data) geo_feature["properties"]["pop"] = data[max_date] # Add to main dataframe. features.append(geo_feature) # Return as json payload. return flask.jsonify( { "type": "FeatureCollection", "features": features, "properties": { "current_geo": names_by_geo.get(requested_geoDcid, ["Unnamed Area"])[0] } }, 200)
def choropleth_geo(): """Returns data for geographic subregions for a certain statistical variable. API Params: geoDcid: The currently viewed geography to render, as a string. level: The subgeographic level to pull and display information for, as a string. Choices: Country, AdministrativeLevel[1/2], City. mdom: The measurement denominator to use as a string. Defaults to "Count_Person". API Returns: geoJson: geoJson format that includes statistical variables info, geoDcid, and name for all subregions. """ # Get required request parameters. requested_geoDcid = flask.request.args.get("geoDcid") if not requested_geoDcid: return flask.jsonify({"error": "Must provide a 'geoDcid' field!"}, 400) display_level = get_sublevel(requested_geoDcid, flask.request.args.get("level")) # Get optional fields. measurement_denominator = flask.request.args.get("mdom", default="Count_Person") # Get list of all contained places. # TODO(iancostello): Handle a failing function call. geos_contained_in_place = dc.get_places_in( [requested_geoDcid], display_level)[requested_geoDcid] # Download statistical variable, names, and geojson for subgeos. # TODO(iancostello): Handle failing function calls. # Also, handle the case where only a fraction of values are returned. names_by_geo = dc.get_property_values( geos_contained_in_place + [requested_geoDcid], "name") geojson_by_geo = dc.get_property_values(geos_contained_in_place, "geoJsonCoordinates") # Download population data if per capita. # TODO(iancostello): Determine how to handle populations # and statistical values from different times. population_by_geo = dc.get_stats(geos_contained_in_place, measurement_denominator) # Process into a combined json object. features, values = [], [] for geo_id, json_text in geojson_by_geo.items(): # Valid response needs at least geometry and a name. if json_text and geo_id in names_by_geo: geo_feature = { "type": "Feature", "geometry": { "type": "MultiPolygon", }, "id": geo_id, "properties": { # Choose the first name when multiple are present. # TODO(iancostello): Implement a better approach. "name": names_by_geo[geo_id][0], "hasSublevel": (display_level in LEVEL_MAP), "geoDcid": geo_id, } } # Load, simplify, and add geoJSON coordinates. # First returned value is chosen always. # TODO(iancostello): Implement a better approach. geojson = json.loads(json_text[0]) geo_feature['geometry']['coordinates'] = ( coerce_geojson_to_righthand_rule(geojson['coordinates'], geojson['type'])) # Process Statistical Observation if valid. if ('data' in population_by_geo.get(geo_id, [])): # Grab the latest available data. geo_feature["properties"]["pop"] = next( iter(reversed(population_by_geo[geo_id]['data'].values()))) # Add to main dataframe. features.append(geo_feature) # Return as json payload. return flask.jsonify( { "type": "FeatureCollection", "features": features, "properties": { # TODO(iancostello): Don't just pick the first and check. "current_geo": names_by_geo[requested_geoDcid][0] } }, 200)