streetlevel.lookaround: Apple Look Around

Support for Apple Look Around.

Note that, unlike with the other providers, this library does not automatically stitch the panoramas since 1) Look Around does not serve one image broken up into tiles, but six faces which form a sort-of-but-not-really cubemap, and 2) its image format, HEIC, is somewhat inconvenient to work with. A function to create an equirectangular image out of the panorama faces is available, but not exactly fast, so it is recommended to display the faces unmodified.

Panoramas can be rendered by creating a spherical rectangle (meaning a rectangle on the surface of a sphere) for each face, centered on phi=0, theta=0. fov_s is the phi size (width), fov_h is the theta size (height), and cy is an additional offset which must be subtracted from phi. The resulting geometry for the face is then rotated by the specified Euler angles. (The API returns several other parameters as well, but they do not appear to be in use.)

Finding panoramas

get_coverage_tile(tile_x, tile_y, session=None)

Fetches Look Around panoramas on a specific map tile. Coordinates are in Slippy Map aka XYZ format at zoom level 17.

Parameters:
  • tile_x (int) – X coordinate of the tile.

  • tile_y (int) – Y coordinate of the tile.

  • session (Session | None) – (optional) A requests session.

Returns:

A CoverageTile object holding a list of panoramas and the last modification date of the tile.

Return type:

CoverageTile

Usage sample:

from streetlevel import lookaround

tile = lookaround.get_coverage_tile(109775, 56716)
first = tile.panos[0]
print(f"""
Got {len(tile.panos)} panoramas. Here's one of them:
ID: {first.id}\t\tBuild ID: {first.build_id}
Latitude: {first.lat}\tLongitude: {first.lon}
Capture date: {first.date}
""")
async get_coverage_tile_async(tile_x, tile_y, session)
Parameters:
  • tile_x (int)

  • tile_y (int)

  • session (ClientSession)

Return type:

CoverageTile

get_coverage_tile_by_latlon(lat, lon, session=None)

Same as get_coverage_tile, but for fetching the tile on which a point is located.

Parameters:
  • lat (float) – Latitude of the point.

  • lon (float) – Longitude of the point.

  • session (Session | None) – (optional) A requests session.

Returns:

A CoverageTile object holding a list of panoramas and the last modification date of the tile. Note that the list is not sorted - the panoramas are in the order in which they were returned by the API.

Return type:

CoverageTile

Usage sample:

from streetlevel import lookaround

tile = lookaround.get_coverage_tile_by_latlon(23.53239040648735, 121.5068719584602)
first = tile.panos[0]
print(f"""
Got {len(tile.panos)} panoramas. Here's one of them:
ID: {first.id}\t\tBuild ID: {first.build_id}
Latitude: {first.lat}\tLongitude: {first.lon}
Capture date: {first.date}
""")
async get_coverage_tile_by_latlon_async(lat, lon, session)
Parameters:
  • lat (float)

  • lon (float)

  • session (ClientSession)

Return type:

CoverageTile

Downloading panoramas

get_panorama_face(pano, face, zoom, auth, session=None)

Fetches one face of a panorama.

Images are in HEIC format. Since HEIC is poorly supported across the board, decoding the image is left to the user of the library.

Parameters:
  • pano (LookaroundPanorama | Tuple[int, int]) – The panorama, or its ID.

  • face (Face | int) – Index of the face.

  • zoom (int) – The zoom level. 0 is highest, 7 is lowest.

  • auth (Authenticator) – An Authenticator object.

  • session (Session | None) – (optional) A requests session.

Returns:

The HEIC file containing the face, as bytes.

Return type:

bytes

Usage sample:

from streetlevel import lookaround

tile = lookaround.get_coverage_tile_by_latlon(46.52943, 10.45544)

auth = lookaround.Authenticator()
faces = []
zoom = 0
for face_idx in range(0, 6):
    face = lookaround.get_panorama_face(tile.panos[0], face_idx, zoom, auth)
    faces.append(face)
download_panorama_face(pano, path, face, zoom, auth, session=None)

Downloads one face of a panorama to a file.

Parameters:
  • pano (LookaroundPanorama | Tuple[int, int]) – The panorama, or its ID.

  • path (str) – Output path.

  • face (Face | int) – Index of the face.

  • zoom (int) – The zoom level. 0 is highest, 7 is lowest.

  • auth (Authenticator) – An Authenticator object.

  • session (Session | None) – (optional) A requests session.

Return type:

None

Usage sample:

from streetlevel import lookaround

tile = lookaround.get_coverage_tile_by_latlon(46.52943, 10.45544)

auth = lookaround.Authenticator()
zoom = 0
for face_idx in range(0, 6):
    lookaround.download_panorama_face(tile.panos[0],
        f"{tile.panos[0].id}_{face_idx}_{zoom}.heic",
        face_idx, zoom, auth)

Data classes and Enums

class CameraMetadata(lens_projection: 'LensProjection', position: 'OrientedPosition')
Parameters:
lens_projection: LensProjection
position: OrientedPosition
class CoverageTile(x, y, panos, last_modified)

Represents a coverage tile.

Parameters:
x: int

The X coordinate of the tile at z=17.

y: int

The Y coordinate of the tile at z=17.

panos: List[LookaroundPanorama]

Panoramas on this tile.

last_modified: datetime

The time the tile was last changed. This happens whenever footage is published or removed, new blurs are applied, or (I assume) PoIs are updated.

class CoverageType(value)

Coverage type of a Look Around panorama.

CAR = 2

The panorama was taken by a car.

BACKPACK = 3

The panorama was taken by a backpack.

class Face(value)

Face indices of a Look Around panorama.

BACK = 0
LEFT = 1
FRONT = 2
RIGHT = 3
TOP = 4
BOTTOM = 5
class LookaroundPanorama(id, build_id, lat, lon, coverage_type=None, date=None, has_blurs=None, raw_orientation=None, raw_altitude=None, tile=None, camera_metadata=None, _heading=None, _pitch=None, _roll=None, _elevation=None, _altitude=None)

Metadata of a Look Around panorama.

Parameters:
  • id (int)

  • build_id (int)

  • lat (float)

  • lon (float)

  • coverage_type (CoverageType)

  • date (datetime)

  • has_blurs (bool)

  • raw_orientation (Tuple[int, int, int])

  • raw_altitude (int)

  • tile (Tuple[int, int, int])

  • camera_metadata (List[CameraMetadata])

  • _heading (float)

  • _pitch (float)

  • _roll (float)

  • _elevation (float)

  • _altitude (float)

build_id: int

An additional parameter required for requesting the imagery. Each time Apple publishes or updates a set of panoramas, they are assigned a build ID to act as a revision number.

camera_metadata: List[CameraMetadata] = None

Properties needed for rendering the panorama faces.

coverage_type: CoverageType = None

Whether the coverage was taken by car or by backpack.

date: datetime = None

Capture date and time of the panorama (in UTC, not local time).

property elevation: float

Elevation at the capture location in meters.

has_blurs: bool = None

Whether something in this panorama, typically a building, has been blurred.

property heading: float

Heading in radians, where 0° is north, 90° is west, 180° is south, 270° is east.

id: int

The pano ID.

lat: float

Latitude of the panorama’s location.

lon: float

Longitude of the panorama’s location.

Creates a link which will open a panorama at this location in Apple Maps. Linking to a specific panorama by its ID does not appear to be possible.

On non-Apple devices, the link will redirect to Google Maps.

Parameters:
  • heading (float) – (optional) Initial heading of the viewport. Defaults to 0°.

  • pitch (float) – (optional) Initial pitch of the viewport. Defaults to 0°.

  • radians (bool) – (optional) Whether angles are in radians. Defaults to False.

  • self (LookaroundPanorama)

Returns:

An Apple Maps URL which will open the closest panorama to this location.

Return type:

str

property pitch: float

Pitch offset for upright correction of the panorama, in radians.

raw_altitude: int = None

The raw altitude value returned by the API.

raw_orientation: Tuple[int, int, int] = None

The raw yaw, pitch, and roll values returned by the API.

property roll: float

Roll offset for upright correction of the panorama, in radians.

tile: Tuple[int, int, int] = None

The tile this panorama is located on.

class LensProjection(fov_s: 'float', fov_h: 'float', k2: 'float', k3: 'float', k4: 'float', cx: 'float', cy: 'float', lx: 'float', ly: 'float')
Parameters:
  • fov_s (float)

  • fov_h (float)

  • k2 (float)

  • k3 (float)

  • k4 (float)

  • cx (float)

  • cy (float)

  • lx (float)

  • ly (float)

fov_s: float

Phi size of the panorama face.

fov_h: float

Theta size of the panorama face.

k2: float
k3: float
k4: float
cx: float

Theta offset.

cy: float

Phi offset.

lx: float
ly: float
class OrientedPosition(x, y, z, yaw, pitch, roll)

Position and rotation of a panorama face in the scene. Angles are in radians.

Parameters:
  • x (float)

  • y (float)

  • z (float)

  • yaw (float)

  • pitch (float)

  • roll (float)

x: float
y: float
z: float
yaw: float
pitch: float
roll: float

Reprojection

to_equirectangular(faces, camera_metadata)

Reprojects the faces of a Look Around panorama to a single equirectangular image. The center of the returned image is the inverse direction of travel (meaning you’re looking backwards).

Note that this method is very slow. On my machine, a full resolution image (zoom 0, 16384×8192) takes about 50 seconds to convert.

PyTorch must be installed to call this function. Due to its size and differing CUDA versions, it is not installed automatically alongside the other dependencies of this library.

Parameters:
  • faces (List[Image]) – The faces of the panorama as PIL images.

  • camera_metadata (List[CameraMetadata]) – The camera metadata of the faces.

Returns:

The reprojected panorama as PIL image.

Return type:

Image

Usage sample:

from streetlevel import lookaround

panos = lookaround.get_coverage_tile_by_latlon(54.583244, 9.820249)
pano = next(p for p in panos if p.id == 6651337760361848285)

auth = lookaround.Authenticator()
faces = []
zoom = 2
for face_idx in range(0, 6):
    face_heic = lookaround.get_panorama_face(pano, face_idx, zoom, auth)
    # Convert the HEIC file to a PIL image here.
    # This step is left to the user so that you can choose whichever
    # library performs best on your machine.
    faces.append(face)

result = lookaround.to_equirectangular(faces, pano.camera_metadata)
result.save(f"{pano.id}_{zoom}.jpg", options={"quality": 100})

Authentication

class Authenticator

Various requests to internal Apple Maps endpoints, such as Look Around imagery, must be dynamically authenticated with a session ID and access key. This class provides this functionality.

authenticate_url(url)

Appends authentication parameters to a URL.

Parameters:

url (str) – An unauthenticated URL.

Returns:

An authenticated URL.

Return type:

str

Miscellaneous

Creates a link which will open a panorama at the given location in Apple Maps. Linking to a specific panorama by its ID does not appear to be possible.

On non-Apple devices, the link will redirect to Google Maps.

Parameters:
  • lat (float) – Latitude of the panorama’s location.

  • lon (float) – Longitude of the panorama’s location.

  • heading (float) – (optional) Initial heading of the viewport. Defaults to 0°.

  • pitch (float) – (optional) Initial pitch of the viewport. Defaults to 0°.

  • radians (bool) – (optional) Whether angles are in radians. Defaults to False.

Returns:

An Apple Maps URL which will open the closest panorama to the given location.

Return type:

str