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) # For some places, lat long is attached to the place node, but for other # places, the lat long is attached to the location value of the place node. # If a place has location, we will use the location value to find the lat # and long. # eg. epaGhgrpFacilityId/1003010 has latitude and longitude but no location # epa/120814013 which is an AirQualitySite has a location, but no latitude # or longitude location_by_geo = dc_service.get_property_values(geos, "location") # dict of <dcid used to get latlon>: <dcid of the place> geo_by_latlon_subject = {} for geo_dcid in geos: if geo_dcid in location_by_geo and len( location_by_geo.get(geo_dcid)) > 0: location_dcid = location_by_geo[geo_dcid][0] geo_by_latlon_subject[location_dcid] = geo_dcid else: geo_by_latlon_subject[geo_dcid] = geo_dcid lat_by_subject = dc_service.get_property_values( list(geo_by_latlon_subject.keys()), "latitude") lon_by_subject = dc_service.get_property_values( list(geo_by_latlon_subject.keys()), "longitude") map_points_list = [] for subject_dcid, latitude in lat_by_subject.items(): longitude = lon_by_subject.get(subject_dcid, []) if len(latitude) == 0 or len(longitude) == 0: continue if not is_float(latitude[0]) or not is_float(longitude[0]): continue geo_id = geo_by_latlon_subject.get(subject_dcid, "") 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 get_state_code(dcids): """Get state codes for a list of places that are state equivalents Args: dcids: ^ separated string of dcids of places that are state equivalents Returns: A dictionary of state codes, keyed by dcid """ result = {} if not dcids: return result dcids = dcids.split('^') iso_codes = dc.get_property_values(dcids, 'isoCode', True) for dcid in dcids: state_code = None iso_code = iso_codes[dcid] if iso_code: split_iso_code = iso_code[0].split("-") if len(split_iso_code ) > 1 and split_iso_code[0] == US_ISO_CODE_PREFIX: state_code = split_iso_code[1] result[dcid] = state_code return result
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 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 get_sublevel(requested_geoDcid, display_level): """Returns the best sublevel display for a geoDcid. Args: requested_geoDcid: The parent geo DCID to find children. display_level: Display level provided or None. Valid display_levels are [AdministrativeLevel1, AdministrativeLevel2] Returns: Directly returns display_level argument if it is not none. Otherwise the next sublevel below the parent is returned. """ if not display_level: requested_geoDcid_type = dc.get_property_values([requested_geoDcid], "typeOf") for level in requested_geoDcid_type.get(requested_geoDcid, []): if level in LEVEL_MAP: return LEVEL_MAP[level] return display_level
def geojson(dcid): """ Get geoJson data for a given place """ geos, geojson_prop = get_choropleth_places(dcid) if not geos: return Response(json.dumps({}), 200, mimetype='application/json') 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"), "hasSublevel": False, "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'] = ( choropleth_api.coerce_geojson_to_righthand_rule( geojson['coordinates'], geojson['type'])) features.append(geo_feature) return Response(json.dumps({ "type": "FeatureCollection", "features": features, "properties": { "current_geo": dcid } }), 200, mimetype='application/json')
def get_sublevel(requested_geoDcid, display_level): """Returns the best sublevel display for a geoDcid. Args: requested_geoDcid: The parent geo DCID to find children. display_level: Display level provided or None. Valid display_levels are [AdministrativeLevel1, AdministrativeLevel2, City] Returns: Directly returns display_level argument if it is not none. Otherwise the next sublevel below the parent is returned. """ if not display_level: requested_geoDcid_type = dc.get_property_values( [requested_geoDcid], "typeOf")[requested_geoDcid] # TODO(iancostello): Handle a failed function call, e.g., returns None. # TODO(iancostello): Handle the case where display_level is None. for level in requested_geoDcid_type: if level in LEVEL_MAP: return LEVEL_MAP[level] return display_level
def api_nearby_places(dcid): """ Get the nearby places for a given place. """ req_json = { 'dcids': [dcid], 'property': 'nearbyPlaces', 'direction': 'out' } url = dc.API_ROOT + dc.API_ENDPOINTS['get_property_values'] payload = dc.send_request(url, req_json=req_json) prop_values = payload[dcid].get('out') if not prop_values: return json.dumps([]) places = [] for prop_value in prop_values: places.append(prop_value['value'].split('@')) places.sort(key=lambda x: x[1]) dcids = [place[0] for place in places] data = dc.get_property_values(dcids, 'typeOf', True) return json.dumps(data)
def get_data(self): gr = self.get_params.get('gr') is not None # Growth Rate dcids = self.get_params.getlist('mid') place_args = {'': (dcids, ch.parse_pop_obs_args(self.get_params, ''))} place_dcid = dcids[0] pc = self.get_params.get('pc') num_to_include = self.get_params.get('n') num_to_include = int( num_to_include if num_to_include else ch.DEFAULT_NUM_BARS) po_args = ch.parse_pop_obs_args(self.get_params) chart_type = self.get_params.get('t') logging.info('chart type %s', chart_type) if chart_type == 'mp': plot_data, dcid_name = self.get_plot_data(place_args, pc, gr) return self.render_chart_mp(plot_data, dcid_name, dcids, po_args['observationDate']) elif chart_type == 'op': place_type = self.get_params.get('placet') order = self.get_params.get('order') is_top = (order if order else '').lower() == 'highest' is_comp = self.get_params.get('comp') is not None if po_args['popType'] == 'AcademicAssessmentEvent': if po_args['constraints']['schoolSubject'] == 'Mathematics': place_vals = [(u'nces/260110307481', 1503.7), (u'nces/260023201202', 1500.1), (u'nces/260110307754', 1497.0), ('nces/260027801484', 1495.2)] place_names = { 'nces/260110307481': 'Bates Academy', 'nces/260023201202': 'Detroit Edison Public School Academy', 'nces/260110307754': 'Clippert Academy', 'nces/260027801484': 'Detroit Merit Charter Academy' } else: place_vals = [(u'nces/260110307481', 1496.6), (u'nces/260110304675', 1490.1), (u'nces/260023201202', 1486.9), ('nces/260013908078', 1484.7)] place_names = { 'nces/260110307481': 'Bates Academy', 'nces/260110304675': 'Chrysler Elementary School', 'nces/260023201202': 'Detroit Edison Public School Academy', 'nces/260013908078': 'Cesar Chavez Academy Intermediate' } else: # Handle errors better places = ch.get_and_filter_places_in(place_dcid, place_type) place_obs = datacommons.get_place_obs( place_type, po_args['observationDate'], po_args['popType'], po_args['constraints']) place_vals, place_names = ch.filter_place_obs_vals( places, place_obs, po_args) place_pop = None if pc: dcids = place_vals place_population = ch.get_place_population(dcids) od = po_args['observationDate'] place_pop = { dcid: place_population[(dcid, od)] for dcid in place_vals } place_vals = per_capita(place_vals, place_pop) place_vals = top_values(place_vals, num_to_include, is_top, is_comp) return self.render_chart_op(place_vals, place_names, num_to_include, is_comp) elif chart_type == 'rp': if pc: try: pc = int(pc) assert pc >= 1 except: raise ValueError( 'pc option must be a positive integer: %r' % pc) place_ranks = { 'geoId/2603000': (999, 'USA Cities'), # Ann Arbor, among all cities 'geoId/0649670': (999, 'USA Cities'), # MTV, among all cities 'geoId/26': (999, 'USA States'), # Michigan, among all States 'geoId/2622000': (999, 'USA Cities') # Detroit } DETROIT_5G_MATH_CPV = { 'assessmentType': 'MichiganStudentTestOfEducationalProgress', 'schoolGradeLevel': 'SchoolGrade5', 'schoolSubject': 'Mathematics' } DETROIT_5G_ELA_CPV = { 'assessmentType': 'MichiganStudentTestOfEducationalProgress', 'schoolGradeLevel': 'SchoolGrade5', 'schoolSubject': 'EnglishLanguageArts' } if (place_dcid == 'geoId/2622000' and po_args['popType'] == 'AcademicAssessmentEvent' and po_args['measuredProp'] == 'scaledScore' and po_args['statType'] == 'meanValue' and po_args['observationDate'] == "2019"): # make sure our hardcoding doesn't overtrigger places = ['geoId/2622000', 'geoId/26163', 'geoId/26'] if po_args['constraints'] == DETROIT_5G_MATH_CPV: place_vals = { 'geoId/2622000': 1463.2, 'geoId/26163': 1480.1, 'geoId/26': 1487.6 } elif po_args['constraints'] == DETROIT_5G_ELA_CPV: place_vals = { 'geoId/2622000': 1471.9, 'geoId/26163': 1488.9, 'geoId/26': 1496.0 } else: places = [place_dcid] + ch.get_ancestor_places(place_dcid) # pl_dcid: pop_dcid place_pops = datacommons.get_populations( places, po_args['popType'], po_args['constraints']) # pop_dcid: obs_val pop_obs = datacommons.get_observations( place_pops.values(), po_args['measuredProp'], po_args['statType'], po_args['observationDate'], po_args['observationPeriod'], po_args['measurementMethod']) # pl_dcid: obs_val place_vals = { pl_dcid: pop_obs[pop_dcid] for pl_dcid, pop_dcid in place_pops.items() } # Hardcoding, but ultimately changed again in render_chart_rp due to pc place_vals['geoId/26'] = -999 place_vals['country/USA'] = -999 place_names = { k: v[0] for k, v in datacommons.get_property_values(places, 'name').items() } if pc: place_population = ch.get_place_population(places) population_od = po_args['observationDate'][:4] # TODO(b/149601841): URGENT--replace this with reading latest_obs_date if population_od > '2018': population_od = '2018' place_pop = { dcid: place_population[(dcid, population_od)] for dcid in places } place_vals = per_capita(place_vals, place_pop, factor=pc) # Order by place type ordered_place_data = collections.OrderedDict() for p in places: ordered_place_data[p] = { 'val': place_vals.get(p), 'rank': place_ranks.get(p), 'name': place_names.get(p) } return self.render_chart_rp(ordered_place_data) elif chart_type == 'av': pop_obs = datacommons.get_pop_obs(place_dcid) pops = pop_obs['populations'] kept_pop_ids, cp = filter_pops_obs(pops, po_args) kept_obs = {} for pop_id in kept_pop_ids: obs = filter_obs(pops[pop_id], po_args) if obs: v = pops[pop_id]['propertyValues'][cp] kept_obs[v] = obs if pc: place_population = ch.get_place_population([place_dcid]) od = po_args['observationDate'] scale = place_population[(place_dcid, od)] else: scale = 1 return self.render_chart_av(kept_obs, num_to_include, po_args['statType'], cp, scale)
def get_property_value(dcid, prop, out=True): return dc.get_property_values([dcid], prop, out)[dcid]
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)
def get_property_value(dcids, prop): """Returns the property values for given node dcids and property label.""" response = dc.get_property_values(dcids.split('^'), prop) return Response(json.dumps(response), 200, mimetype='application/json')