Skip to content

Commit

Permalink
[Naver] Update to v3 of the API
Browse files Browse the repository at this point in the history
  • Loading branch information
sk-zk committed Dec 17, 2024
1 parent ff5517b commit 5e04ae5
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 63 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ Services covering multiple countries are on the left; services covering one spec
<td></td>
<td align="center">✔</td>
<td align="center">✔<sup>7</sup></td>
<td align="center">✔<sup>7</sup></td>
<td align="center">✔<br></td>
<td align="center">✔<sup>11</sup></td>
<td align="center">✔</td>
<td align="center">✔<sup>7</sup></td>
</tr>
<tr>
Expand All @@ -196,7 +196,7 @@ Services covering multiple countries are on the left; services covering one spec
<td></td>
<td align="center">✔</td>
<td align="center">⚫</td>
<td align="center"></td>
<td align="center">⚫<sup>12</sup></td>
<td align="center">✔</td>
<td align="center">⚫</td>
</tr>
Expand Down Expand Up @@ -276,4 +276,6 @@ Services covering multiple countries are on the left; services covering one spec
7: Only heading; pitch/roll do not appear to be available
8: Previous and next image in sequence
9: Month and year only
10: There is a `has_depth` field in the raw metadata, but I've yet to find a panorama that actually has depth.
10: There is a `has_depth` field in the raw metadata, but I've yet to find a panorama that actually has depth
11: Pitch/roll are only available for the new 3D imagery
12: Camera altitude is available, however
8 changes: 4 additions & 4 deletions streetlevel/naver/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ def build_find_panorama_request_url(lat: float, lon: float) -> str:


def build_find_panorama_by_id_request_url(panoid: str, language: str) -> str:
return f"https://panorama.map.naver.com/metadata/basic/{panoid}?lang={language}&version=2.1.0"
return f"https://panorama.map.naver.com/metadataV3/basic/{panoid}?lang={language}"


def build_timeline_request_url(panoid: str) -> str:
return f"https://panorama.map.naver.com/metadata/timeline/{panoid}"
def build_timeline_request_url(timeline_id: str) -> str:
return f"https://panorama.map.naver.com/metadata/timeline/{timeline_id}"


def build_around_request_url(panoid: str) -> str:
return f"https://panorama.map.naver.com/metadata/around/{panoid}?lang=ko"
return f"https://panorama.map.naver.com/metadataV3/around/{panoid}?lang=ko"


def build_depth_request_url(panoid: str) -> str:
Expand Down
40 changes: 29 additions & 11 deletions streetlevel/naver/panorama.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from dataclasses import dataclass
from datetime import datetime
from enum import IntEnum
from typing import List
from typing import List, Optional

import numpy as np

Expand All @@ -27,7 +27,11 @@ class PanoramaType(IntEnum):
INDOOR_HIGH = 11 #:
UNDERWATER = 12 #:
TREKKER = 13 #:
MESH_EQUIRECT = 15 #:
MESH_EQUIRECT = 15
"""
An Apple-like panorama which can be fetched in equirectangular projection
and for which a 3D mesh is available.
"""
INDOOR_3D = 100 #:


Expand All @@ -49,18 +53,29 @@ class NaverPanorama:

heading: float = None
"""Heading in radians, where 0° is north, 90° is east, 180° is south and 270° is west."""
elevation: float = None
"""Elevation at the capture location in meters."""
camera_height: float = None
"""Height of the camera in meters above ground."""
altitude: float = None
"""Altitude of the camera above sea level, in meters."""
pitch: float = None
"""
Pitch offset for upright correction of the panorama, in radians.
Only available if ``has_equirect`` is True; the field is set to 0 otherwise.
"""
roll: float = None
"""
Roll offset for upright correction of the panorama, in radians.
Only available if ``has_equirect`` is True; the field is set to 0 otherwise.
"""

max_zoom: int = None
"""Highest zoom level available for this panorama."""

neighbors: Neighbors = None
"""A list of nearby panoramas."""
links: List[Link] = None
"""The panoramas which the white dots in the client link to."""
"""
The panoramas which the white dots in the pre-3D client link to.
This appears to be unused in the new client.
"""
historical: List[NaverPanorama] = None
"""A list of panoramas with a different date at the same location.
Only available if the panorama was retrieved by ``find_panorama_by_id``."""
Expand All @@ -80,11 +95,16 @@ class NaverPanorama:
"""The title field, which typically contains the street name."""

depth: np.ndarray = None
"""The depth maps of the faces."""
"""The legacy depth maps of the cubemap faces."""

has_equirect: bool = None
"""
If True, this panorama can be fetched as either in equirectangular projection or as a cubemap.
If False, only a cubemap is available.
"""
panorama_type: PanoramaType = None
"""The panorama type. Most identifiers are taken directly from the source."""
overlay: Overlay = None
overlay: Optional[Overlay] = None
"""
Curiously, in pre-3D imagery, Naver masks their car twice: once with an image of a car baked into the panorama,
and additionally with an image of the road beneath it (like Google and Apple), which is served as a separate file
Expand Down Expand Up @@ -121,7 +141,6 @@ def __str__(self):
@dataclass
class Overlay:
"""URLs to the images from which the overlay hiding the mapping car is created."""

source: str
"""URL to the texture."""
mask: str
Expand All @@ -131,7 +150,6 @@ class Overlay:
@dataclass
class Neighbors:
"""Nearby panoramas."""

street: List[NaverPanorama]
"""Nearby panoramas taken at street level."""
other: List[NaverPanorama]
Expand Down
105 changes: 61 additions & 44 deletions streetlevel/naver/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,74 @@
from typing import List, Optional

from streetlevel.dataclasses import Link
from streetlevel.geo import get_bearing
from streetlevel.naver.panorama import Neighbors, NaverPanorama, PanoramaType, Overlay


def parse_panorama(response: dict) -> NaverPanorama:
basic = response["basic"]
elevation = basic["land_altitude"] * 0.01
altitude = response["altitude"]
if response["camera_angle"][0] != 0:
pitch = math.radians(90 - response["camera_angle"][0])
roll = math.radians(90 - response["camera_angle"][2])
else:
pitch = 0
roll = 0
pano = NaverPanorama(
id=basic["id"],
lat=basic["latitude"],
lon=basic["longitude"],
heading=math.radians(basic["camera_angle"][1]),
max_zoom=int(basic["image"]["segment"]) // 2,
timeline_id=basic["timeline_id"],
date=_parse_date(basic["photodate"]),
is_latest=basic["latest"],
description=basic["description"],
title=basic["title"],
panorama_type=PanoramaType(int(basic["dtl_type"])),
elevation=elevation,
camera_height=(basic["camera_altitude"] * 0.01) - elevation
id=response["id"],
lat=response["latitude"],
lon=response["longitude"],
heading=math.radians(response["camera_angle"][1]),
pitch=pitch,
roll=roll,
max_zoom=int(response["segment"]) // 2,
timeline_id=response["info"]["timeline_id"],
date=_parse_date(response["info"]["photodate"]),
is_latest=response["info"]["latest"],
description=response["info"]["description"],
title=response["info"]["title"],
panorama_type=PanoramaType(int(response["dtl_type"])),
altitude=altitude,
has_equirect=response["proj_type"] == "equirect"
)

if len(basic["image"]["overlays"]) > 1:
if response["overlay_type"] == "car":
pano.overlay = Overlay(
"https://panorama.map.naver.com" + basic["image"]["overlays"][1][0],
"https://panorama.map.naver.com" + basic["image"]["overlays"][1][1])
f"https://panorama.map.naver.com/api/v2/overlays/floor/{pano.id}",
f"https://panorama.map.naver.com/resources/style/mask.png"
)

pano.links = _parse_links(basic["links"])
pano.links = _parse_links(response["links"], pano.lat, pano.lon)

return pano


def parse_neighbors(response: dict, parent_id: str) -> Neighbors:
street = _parse_neighbor_section(response, "street", parent_id)
other = _parse_neighbor_section(response, "air", parent_id)
if "street" in response["panoramas"]:
street = _parse_neighbor_section(response["panoramas"]["street"], parent_id)
else:
street = None

if "air" in response["panoramas"]:
other = _parse_neighbor_section(response["panoramas"]["air"], parent_id)
else:
other = None

return Neighbors(street, other)


def _parse_neighbor_section(response: dict, section: str, parent_id: str) -> List[NaverPanorama]:
def _parse_neighbor_section(section: dict, parent_id: str) -> List[NaverPanorama]:
panos = []
if section in response["around"]["panoramas"]:
for raw_pano in response["around"]["panoramas"][section][1:]:
if raw_pano[0] == parent_id:
continue
elevation = raw_pano[4] * 0.01
pano = NaverPanorama(
id=raw_pano[0],
lat=raw_pano[2],
lon=raw_pano[1],
elevation=elevation,
camera_height=(raw_pano[3] * 0.01) - elevation)
panos.append(pano)
for raw_pano in section:
if raw_pano["id"] == parent_id:
continue
pano = NaverPanorama(
id=raw_pano["id"],
lat=raw_pano["latitude"],
lon=raw_pano["longitude"],
altitude=raw_pano["altitude"],
panorama_type=PanoramaType(int(raw_pano["dtl_type"])),
)
panos.append(pano)
return panos


Expand All @@ -80,7 +96,7 @@ def parse_nearby(response: dict) -> NaverPanorama:
date=_parse_date(feature["properties"]["photodate"]),
description=feature["properties"]["description"],
title=feature["properties"]["title"],
elevation=elevation,
altitude=elevation,
camera_height=(feature["properties"]["camera_altitude"] * 0.01) - elevation,
panorama_type=PanoramaType(int(feature["properties"]["type"])),
)
Expand All @@ -90,18 +106,19 @@ def _parse_date(date_str: str) -> datetime:
return datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")


def _parse_links(links_json: List) -> Optional[List[Link]]:
def _parse_links(links_json: List, pano_lat: float, pano_lon: float) -> Optional[List[Link]]:
if len(links_json) < 2:
return None

links = []
for linked_json in links_json[1:]:
linked = NaverPanorama(
id=linked_json[0],
title=linked_json[1],
lat=linked_json[5],
lon=linked_json[4],
for link_json in links_json:
link = NaverPanorama(
id=link_json["id"],
lat=link_json["latitude"],
lon=link_json["longitude"],
panorama_type=PanoramaType(int(link_json["dtl_type"])),
altitude=link_json["altitude"]
)
angle = math.radians(float(linked_json[2]))
links.append(Link(linked, angle))
angle = get_bearing(pano_lat, pano_lon, link.lat, link.lon)
links.append(Link(link, angle))
return links

0 comments on commit 5e04ae5

Please sign in to comment.