import placekey as pk
import geopandas as gpd
import numpy as np
import folium
We've included a data file that contains Census block group (CBG) geometries for California. The original file and more are available from the Census website.
cbgs = gpd.read_file('../data/CA_2019_census_block_groups_sample/').set_index('GEOID')
cbgs.head()
The row with GEOID == 060750124022
corresponds to the CBG that contains San Francisco City Hall, which is the one we want to approximate. GeoPandas stores the geometry of the CBGs in the geometry
column as Shapely Polygons.
cbg_geometry = cbgs.loc['060750124022']['geometry']
cbg_geometry
It's nice that Shapely draws the polygon for us, but let's see what it looks like on a map.
cbg_centroid = next(zip(*cbg_geometry.centroid.xy)) # This is a (long, lat) tuple
cbg_map = folium.Map(cbg_centroid[::-1], zoom_start=16, tiles='cartodbpositron')
folium.GeoJson(cbg_geometry).add_to(cbg_map)
cbg_map
The function polygon_to_placekeys()
generates the set of Placekeys which cover a given polygon. This set is split into two disjoint subsets:
There is an optional parameter for this function, include_touching
, which when True
will include Placekeys that intersect the polygon, but have 0% of their area contained in the polygon (e.g. they only share boundary points). In case you are working with polygons specified by WKTs or GeoJSONs, there are equivalent functions for those input types (wkt_to_placekeys()
and geojson_to_placekeys()
).
covering_placekeys = pk.polygon_to_placekeys(cbg_geometry, geo_json=True)
covering_placekeys
Before we continue, here's a function for plotting Placekeys. It can be used on its own, or passed another Folium map to add the Placekeys to.
def draw_placekeys(placekey_values, zoom_start=18, folium_map=None, hex_color='lightblue', weight=2, labels=False):
"""
:param placekey_values: A list of Placekey strings
:param zoom_start: Folium zoom level. 18 is suitable for neighboring resolution 10 H3s.
:folium_map: A Folium map object to add the Placekeys to
:labels: Whether or not to add labels for Placekeys
:return: a Folium map object
"""
geos = [pk.placekey_to_geo(p) for p in placekey_values]
hexagons = [pk.placekey_to_hex_boundary(p) for p in placekey_values]
if folium_map is None:
centroid = np.mean(geos, axis=0)
folium_map = folium.Map((centroid[0], centroid[1]), zoom_start=zoom_start, tiles='cartodbpositron')
for h in hexagons:
folium.Polygon(
locations=h,
weight=weight,
color=hex_color
).add_to(folium_map)
if labels:
for p, g in zip(placekey_values, geos):
icon = folium.features.DivIcon(
icon_size=(120, 36),
icon_anchor=(60, 15),
html='<div style="align: center; font-size: 12pt; background-color: lightblue; border-radius: 5px; padding: 2px">{}</div>'.format(p),
)
folium.map.Marker(
[g[0], g[1]],
icon=icon
).add_to(folium_map)
return folium_map
pk_cbg_map = draw_placekeys(covering_placekeys['boundary'], folium_map=cbg_map, hex_color='orange')
pk_cbg_map = draw_placekeys(covering_placekeys['interior'], folium_map=pk_cbg_map, hex_color='red')
pk_cbg_map
Similar Placekeys are physically close to each other, and often physically close places will have similar Placekeys (this won't always be true since we're trying to cover the earth with a linear ordering of codes). Below is our earlier example with Placekeys labelled by their Where part.
draw_placekeys(
covering_placekeys['interior'] + covering_placekeys['boundary'],
zoom_start=17, labels=True
)
We provide a function for explicitly computing the distance in meters between two Placekeys based on the centers of the Where parts.
pk.placekey_distance('@5vg-7gq-dn5', '@5vg-7gq-t9z')
The library also contains a table of the maximal distance in meters between two Placekeys based on the length of their common prefix.
pk.get_prefix_distance_dict()
In case of a need to either visualize Placekeys or to serialize their shape in a data file, the Placekey library has several output options for the geometry of a Placekey. The Placekey library by default
The GeoJSON format represents points as (longitude, latitude), so all output functions have a parameter geo_json
which when True
will cause points to be output in this format. Setting this flag will also cause list of boundary points to have the first and last entry the same. Functions with geojson
in their name have geo_json=True
by default, while other functions have geo_json=False
by default. This difference is easy to spot with placekey_to_hex_boundary()
.
pk.placekey_to_hex_boundary('@5vg-7gq-tjv')
pk.placekey_to_hex_boundary('@5vg-7gq-tjv', geo_json=True)
Placekeys can also be output to Shapely polygons, WKT (Well-Known Text), and GeoJSON dictionaries.
pk.placekey_to_polygon('@5vg-7gq-tjv')
pk.placekey_to_wkt('@5vg-7gq-tjv')
pk.placekey_to_geojson('@5vg-7gq-tjv')
The Plackey library can validate the format of Placekeys and the Where part (e.g. "@5vg-7gq-tjv"). Validation of the What part (e.g. "223-227@") can be done via the Placekey service API.
pk.placekey_format_is_valid("223-227@5vg-7gq-tjv")
pk.placekey_format_is_valid("223-227@ima-bad-key")