fix: bind knowledge maps to explicit regions

This commit is contained in:
xuelong committed 2026-08-26 02:26:24 -07:00
1 parent 329a94d457
commit 90f75d41ae
15 files changed
+1126 -275

No files matched your search

+112 -49
View File
@@ -35,6 +35,29 @@ GRAPH_POI_LABELS = {
"BusStop": "公交站",
}
# Projects imported from JSON do not have to use the historical Libo labels.
# The city graph, for example, models every spatial business as ``Place`` and
# stores its business type in ``place_type``. Keep this mapping in the map
# adapter so every map-enabled project can use the same UI template.
GRAPH_PLACE_TYPE_CATEGORIES = {
"eat": "美食",
"drink": "饮品",
"hotel": "酒店",
"mall": "商场",
"sight": "景点",
"transit": "交通设施",
"transit_stop": "交通设施",
"recreation": "体育休闲",
"education": "科教文化",
"medical": "医疗保健",
"government": "政府机构",
"enterprise": "公司企业",
"life": "生活服务",
"residential": "商务住宅",
"facility": "公共设施",
"poi": "其他地点",
}
FOOD_ENRICHMENT_KEYS = (
"food_fusion_status",
"food_match_confidence",
@@ -443,10 +466,12 @@ def _is_enterprise_travel_graph(graph_name: str) -> bool:
def _resolve_spatial_graph_name(graph_name: str) -> str:
"""Map the city project graph name to the materialized spatial POI graph."""
lower = graph_name.lower()
if graph_name == SPATIAL_GRAPH_NAME or lower.endswith("_spatial_v1"):
return graph_name
"""Resolve the explicitly paired city map store used by the legacy project.
``guiyang_new2`` is the semantic city graph while ``guiyang_spatial_v1`` is
its map/POI store. Other map-enabled projects keep using their own graph,
so imported bundles do not depend on a project-name convention.
"""
if graph_name == "guiyang_new2":
return SPATIAL_GRAPH_NAME
return graph_name
@@ -670,6 +695,24 @@ def _graph_poi_enrichment(
return labels, pick(FOOD_ENRICHMENT_KEYS), pick(HOTEL_ENRICHMENT_KEYS)
def _graph_poi_business_categories(
labels: list[str] | tuple[str, ...] | None,
properties: dict[str, Any],
) -> list[str]:
categories = [
GRAPH_POI_LABELS[label]
for label in (labels or [])
if label in GRAPH_POI_LABELS
]
type_label = str(properties.get("type_label") or "").strip()
place_type = str(properties.get("place_type") or "").strip().lower()
if type_label:
categories.insert(0, type_label)
elif place_type:
categories.insert(0, GRAPH_PLACE_TYPE_CATEGORIES.get(place_type, place_type))
return list(dict.fromkeys(category for category in categories if category))
def _graph_poi_categories(graph_name: str) -> dict[str, list[str]]:
db = FalkorDB(
host=settings.falkordb_host,
@@ -677,22 +720,26 @@ def _graph_poi_categories(graph_name: str) -> dict[str, list[str]]:
)
try:
graph = db.select_graph(graph_name)
rows = graph.query(
"MATCH (n) "
"WHERE n.element_id IS NOT NULL "
"AND any(label IN labels(n) WHERE label IN "
"['FoodPlace','Hotel','ScenicSpot','TransitFacility','BusStop']) "
"RETURN n.element_id, labels(n)"
).result_set
result: dict[str, list[str]] = {}
for element_id, labels in rows:
categories = [
GRAPH_POI_LABELS[label]
for label in (labels or [])
if label in GRAPH_POI_LABELS
]
if element_id and categories:
result[str(element_id)] = categories
page_size = 5000
offset = 0
while True:
rows = graph.query(
"MATCH (n) "
"WHERE n.element_id IS NOT NULL "
"AND coalesce(n.lng,n.longitude) IS NOT NULL "
"AND coalesce(n.lat,n.latitude) IS NOT NULL "
"RETURN n.element_id, labels(n), properties(n) "
f"ORDER BY n.element_id SKIP {offset} LIMIT {page_size}"
).result_set
for element_id, labels, raw_properties in rows:
properties = raw_properties if isinstance(raw_properties, dict) else {}
categories = _graph_poi_business_categories(labels, properties)
if element_id and categories:
result[str(element_id)] = categories
if len(rows) < page_size:
break
offset += page_size
return result
finally:
db.close()
@@ -715,16 +762,14 @@ def _graph_map_poi_items(rows: list[list[Any]]) -> list[dict[str, Any]]:
items: list[dict[str, Any]] = []
for node_id, labels, raw_properties in rows:
properties = raw_properties if isinstance(raw_properties, dict) else {}
lng = properties.get("lng")
lat = properties.get("lat")
lng = properties.get("lng", properties.get("longitude"))
lat = properties.get("lat", properties.get("latitude"))
if lng is None or lat is None:
continue
categories = [
GRAPH_POI_LABELS[label]
for label in (labels or [])
if label in GRAPH_POI_LABELS
]
category = str(properties.get("type_label") or (categories[0] if categories else "其他地点"))
categories = _graph_poi_business_categories(labels, properties)
if not categories:
continue
category = categories[0]
element_id = str(
properties.get("element_id")
or node_id
@@ -744,6 +789,8 @@ def _graph_map_poi_items(rows: list[list[Any]]) -> list[dict[str, Any]]:
"address": str(properties.get("address") or ""),
"district": str(properties.get("district") or ""),
"city": str(properties.get("city") or ""),
"province": str(properties.get("province") or ""),
"adcode": str(properties.get("adcode") or ""),
"towncode": str(properties.get("towncode") or ""),
"town_name": str(properties.get("town_name") or ""),
"zone_id": str(properties.get("zone_id") or ""),
@@ -756,15 +803,23 @@ def _graph_map_poi_items(rows: list[list[Any]]) -> list[dict[str, Any]]:
def _read_graph_map_pois(graph_name: str) -> list[dict[str, Any]]:
db = FalkorDB(host=settings.falkordb_host, port=settings.falkordb_port)
try:
rows = db.select_graph(graph_name).query(
"MATCH (n) "
"WHERE n.lng IS NOT NULL AND n.lat IS NOT NULL "
"AND any(label IN labels(n) WHERE label IN "
"['FoodPlace','Hotel','ScenicSpot','TransitFacility','BusStop']) "
"RETURN n.__kg_node_id,labels(n),properties(n) "
"ORDER BY n.name LIMIT 15000"
).result_set
return _graph_map_poi_items(rows)
graph = db.select_graph(graph_name)
items: list[dict[str, Any]] = []
page_size = 5000
offset = 0
while True:
rows = graph.query(
"MATCH (n) "
"WHERE coalesce(n.lng,n.longitude) IS NOT NULL "
"AND coalesce(n.lat,n.latitude) IS NOT NULL "
"RETURN n.__kg_node_id,labels(n),properties(n) "
f"ORDER BY n.name,n.element_id SKIP {offset} LIMIT {page_size}"
).result_set
items.extend(_graph_map_poi_items(rows))
if len(rows) < page_size:
break
offset += page_size
return items
finally:
db.close()
@@ -899,12 +954,16 @@ def _graph_poi_detail_payload(
or properties.get("gaode_poi_id")
or ""
)
categories = _graph_poi_business_categories(labels, properties)
lng = properties.get("lng", properties.get("longitude"))
lat = properties.get("lat", properties.get("latitude"))
return {
"graph_name": graph_name,
"id": element_id,
"gaode_poi_id": str(properties.get("gaode_poi_id") or ""),
"name": str(properties.get("name") or "未命名POI"),
"category": str(properties.get("type_label") or "其他地点"),
"category": categories[0] if categories else "其他地点",
"categories": categories or ["其他地点"],
"place_type": str(properties.get("place_type") or "poi"),
"business_subcategory": raw.get("business_subcategory") or "",
"scenic_type": raw.get("scenic_type") or properties.get("scenic_type") or "",
@@ -920,8 +979,8 @@ def _graph_poi_detail_payload(
"typecode": str(properties.get("typecode") or ""),
"scan_hit_count": raw.get("scan_hit_count") or 0,
"matched_scan_types": raw.get("matched_scan_types") or [],
"lng": float(properties.get("lng") or 0),
"lat": float(properties.get("lat") or 0),
"lng": float(lng or 0),
"lat": float(lat or 0),
"province": str(properties.get("province") or ""),
"city": str(properties.get("city") or ""),
"district": str(properties.get("district") or ""),
@@ -981,8 +1040,8 @@ async def map_pois(
async with conn.cursor() as cur:
await cur.execute(
f"""SELECT p.element_id, p.gaode_poi_id, p.name, p.type_label,
p.place_type, p.lng, p.lat, p.address, p.district,
p.city, p.towncode, p.town_name,
p.place_type, p.lng, p.lat, p.address, p.province,
p.district, p.city, p.adcode, p.towncode, p.town_name,
z.zone_id, z.zone_name
FROM {s}.amap_spatial_pois p
LEFT JOIN {s}.poi_zone_assignments z
@@ -991,18 +1050,20 @@ async def map_pois(
AND z.zone_set_id = %s
WHERE p.graph_name=%s
ORDER BY p.type_label, p.name
LIMIT 15000""",
LIMIT 100000""",
(LIBO_ZONE_SET_ID, graph_name),
)
rows = await cur.fetchall()
if rows:
try:
graph_categories = await asyncio.to_thread(
_graph_poi_categories,
graph_name,
)
except Exception: # noqa: BLE001 - relational points remain usable
graph_categories = {}
graph_categories: dict[str, list[str]] = {}
if graph_name != SPATIAL_GRAPH_NAME:
try:
graph_categories = await asyncio.to_thread(
_graph_poi_categories,
graph_name,
)
except Exception: # noqa: BLE001 - relational points remain usable
graph_categories = {}
items = [
{
"id": row["element_id"],
@@ -1017,8 +1078,10 @@ async def map_pois(
"lng": float(row["lng"]),
"lat": float(row["lat"]),
"address": row["address"] or "",
"province": row["province"] or "",
"district": row["district"] or "",
"city": row["city"] or "",
"adcode": row["adcode"] or "",
"towncode": row["towncode"] or "",
"town_name": row["town_name"] or "",
"zone_id": row["zone_id"] or "",