File size: 2,158 Bytes
9f4f2f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import math
import ee


def gba_tile_id(lon: float, lat: float, step_deg: int = 5) -> str:
    lon0 = math.floor(lon / step_deg) * step_deg
    lat0 = math.floor(lat / step_deg) * step_deg
    lon1 = lon0 + step_deg
    lat1 = lat0 + step_deg

    def fmt_lon(x: int) -> str:
        return ("e" if x >= 0 else "w") + f"{abs(x):03d}"

    def fmt_lat(x: int) -> str:
        return ("n" if x >= 0 else "s") + f"{abs(x):02d}"

    return f"{fmt_lon(lon0)}_{fmt_lat(lat1)}_{fmt_lon(lon1)}_{fmt_lat(lat0)}"


def gba_fc_path(tile_id: str) -> str:
    return f"projects/sat-io/open-datasets/GLOBAL_BUILDING_ATLAS/{tile_id}"


class GlobalBuildingAtlasHeight:
    def __init__(self) -> None:
        self._fc_cache = {}

    def _get_fc(self, tile_id: str) -> ee.FeatureCollection:
        if tile_id not in self._fc_cache:
            self._fc_cache[tile_id] = ee.FeatureCollection(gba_fc_path(tile_id))
        return self._fc_cache[tile_id]

    def get_height_m(self, lat: float, lon: float, buffer_m: float = 20.0, centroid_scale_m: float = 1.0):
        tile_id = gba_tile_id(lon, lat)
        fc = self._get_fc(tile_id)

        pt = ee.Geometry.Point([lon, lat])
        search_geom = pt.buffer(buffer_m) if buffer_m and buffer_m > 0 else pt

        candidates = (
            fc.filterBounds(search_geom)
            .filter(ee.Filter.gt("height", 0))
        )

        def add_dist(f):
            f = ee.Feature(f)
            d = f.geometry().centroid(centroid_scale_m).distance(pt)
            return f.set("dist_m", d)

        best = ee.Feature(candidates.map(add_dist).sort("dist_m").first())

        count = candidates.size().getInfo()
        if count == 0:
            return {
                "status": "not_found",
                "tile_id": tile_id,
                "buffer_m": buffer_m,
                "predicted_height": None,
            }

        props = best.toDictionary(["height", "dist_m"]).getInfo()

        return {
            "status": "success",
            "tile_id": tile_id,
            "buffer_m": buffer_m,
            "predicted_height": props.get("height"),
            "distance_m": props.get("dist_m"),
        }