Skip to content

Commit 5e5aff6

Browse files
committed
feat(harmony): implement camera bounds methods and enhance projection utilities
Implement camera coordinate bounds methods: - Complete C++ implementation of setVisibleCoordinateBounds with LatLng array parsing, EdgeInsets support, and animation options - Add convenience methods getCameraForBounds, getCameraForBoundsWithPadding, and getCameraForBoundsWithOptions for calculating camera positions - Add convenience methods setCameraForBounds, setCameraForBoundsWithPadding, and setCameraForBoundsWithOptions for setting camera with animation support - Add setCameraPosition overloads for immediate jump or animated transition Enhance Projection utilities: - Add screen-location conversion methods (toScreenLocation, fromScreenLocation) - Add batch conversion methods for performance (toScreenLocations, fromScreenLocations) - Add visible region and bounds query methods - Add projected meters conversion utilities - Add static utility methods (bearing, degrees/radians conversion, longitude span) - Improve coordinate transformation capabilities Reference: Android and iOS implementations Files changed: native_map_view_camera.cpp, MapLibreMap.ets, Projection.ets, VisibleRegion.ets
1 parent 3a08b2d commit 5e5aff6

4 files changed

Lines changed: 744 additions & 82 deletions

File tree

platform/harmony/maplibre_harmony/src/main/cpp/core/native_map_view/native_map_view_camera.cpp

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "geometry/lat_lng_harmony.hpp"
55
#include "geometry/point_harmony.hpp"
66
#include "geometry/lat_lng_bounds_harmony.hpp"
7+
#include "geometry/rect_harmony.hpp"
78
#include "camera/camera_position_harmony.hpp"
89
#include "rendering/harmony_renderer.hpp"
910
#include <mbgl/style/style.hpp>
@@ -1236,10 +1237,101 @@ napi_value NativeMapView::resetNorth(napi_env env, napi_callback_info info) {
12361237

12371238
napi_value NativeMapView::setVisibleCoordinateBounds(napi_env env, napi_callback_info info) {
12381239
NapiArgs args(env, info);
1240+
args.RequireMinArgs(4);
1241+
1242+
if (args.HasError()) {
1243+
Logger::error("NativeMapView", "setVisibleCoordinateBounds: Invalid arguments");
1244+
return args.Undefined();
1245+
}
1246+
1247+
// 获取 NativeMapView 实例
1248+
NativeMapView* instance = nullptr;
1249+
if (napi_unwrap(env, args.This(), reinterpret_cast<void**>(&instance)) != napi_ok || !instance->map) {
1250+
Logger::error("NativeMapView", "setVisibleCoordinateBounds: Map not initialized");
1251+
return args.Undefined();
1252+
}
12391253

1240-
// TODO: 需要实现 LatLng 数组和 RectF 的 NAPI 包装类
1241-
// 参考 Android: platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp:583-615
1242-
Logger::warn("NativeMapView", "setVisibleCoordinateBounds: Not implemented - requires LatLng array and RectF wrapper classes");
1254+
try {
1255+
// 1. 解析 LatLng 数组 (参数0)
1256+
napi_value coordsArray = args.Get(0);
1257+
1258+
// 检查是否为数组
1259+
bool isArray = false;
1260+
napi_status status = napi_is_array(env, coordsArray, &isArray);
1261+
if (status != napi_ok || !isArray) {
1262+
Logger::error("NativeMapView", "setVisibleCoordinateBounds: First argument must be an array");
1263+
return args.Undefined();
1264+
}
1265+
1266+
uint32_t count = 0;
1267+
status = napi_get_array_length(env, coordsArray, &count);
1268+
if (status != napi_ok || count == 0) {
1269+
Logger::error("NativeMapView", "setVisibleCoordinateBounds: Empty coordinates array");
1270+
return args.Undefined();
1271+
}
1272+
1273+
std::vector<mbgl::LatLng> latLngs;
1274+
latLngs.reserve(count);
1275+
1276+
for (uint32_t i = 0; i < count; i++) {
1277+
napi_value item;
1278+
status = napi_get_element(env, coordsArray, i, &item);
1279+
if (status == napi_ok) {
1280+
mbgl::LatLng latLng;
1281+
if (LatLngHarmony::ParseLatLng(env, item, latLng)) {
1282+
latLngs.push_back(latLng);
1283+
} else {
1284+
Logger::warn("NativeMapView", "setVisibleCoordinateBounds: Failed to parse LatLng at index %u", i);
1285+
}
1286+
}
1287+
}
1288+
1289+
if (latLngs.empty()) {
1290+
Logger::error("NativeMapView", "setVisibleCoordinateBounds: No valid coordinates");
1291+
return args.Undefined();
1292+
}
1293+
1294+
// 2. 解析 padding (参数1: Rect 对象)
1295+
napi_value paddingObj = args.Get(1);
1296+
mbgl::EdgeInsets padding;
1297+
if (!RectHarmony::ParseAsEdgeInsets(env, paddingObj, padding)) {
1298+
Logger::error("NativeMapView", "setVisibleCoordinateBounds: Failed to parse padding");
1299+
return args.Undefined();
1300+
}
1301+
1302+
// 3. 解析 direction/bearing (参数2)
1303+
double direction = args.GetDoubleOr(2, -1.0);
1304+
1305+
// 4. 解析 duration (参数3)
1306+
int64_t duration = args.GetInt64Or(3, 0);
1307+
1308+
// 5. 在地图线程上执行相机计算和动画
1309+
instance->invokeOnMapThread([latLngs, padding, direction, duration](mbgl::Map* m) {
1310+
// 计算适配所有坐标的相机位置
1311+
mbgl::CameraOptions cameraOptions = m->cameraForLatLngs(latLngs, padding);
1312+
1313+
// 如果指定了 direction,设置 bearing
1314+
if (direction >= 0) {
1315+
cameraOptions.bearing = direction;
1316+
}
1317+
1318+
// 构建动画选项
1319+
mbgl::AnimationOptions animOptions;
1320+
if (duration > 0) {
1321+
animOptions.duration.emplace(mbgl::Milliseconds(duration));
1322+
// 使用与 iOS 相同的缓动函数
1323+
animOptions.easing.emplace(mbgl::util::UnitBezier{0.25, 0.1, 0.25, 0.1});
1324+
}
1325+
1326+
// 执行相机动画
1327+
m->easeTo(cameraOptions, animOptions);
1328+
});
1329+
1330+
Logger::info("NativeMapView", "setVisibleCoordinateBounds: Successfully set camera for %zu coordinates", latLngs.size());
1331+
1332+
} catch (const std::exception& e) {
1333+
Logger::error("NativeMapView", "setVisibleCoordinateBounds: Exception - %s", e.what());
1334+
}
12431335

12441336
return args.Undefined();
12451337
}

platform/harmony/maplibre_harmony/src/main/ets/maps/MapLibreMap.ets

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import maplibre, { NativeMapView } from 'libmaplibre.so';
1+
import maplibre, { EdgeInsets, NativeMapView } from 'libmaplibre.so';
22
import type { Icon, ExpressionLiteral as NapiExpressionLiteral } from 'libmaplibre.so';
33
import { MapLibreMapOptions } from './MapLibreMapOptions';
44
import { CameraPosition } from './camera/CameraPosition';
@@ -1025,6 +1025,143 @@ export class MapLibreMap {
10251025
return result as CameraPosition;
10261026
}
10271027

1028+
/**
1029+
* 获取适配指定坐标边界的相机位置(使用当前 bearing 和 tilt)
1030+
* 参考 Android 的 getCameraForLatLngBounds(LatLngBounds) 方法
1031+
*
1032+
* @param bounds 要适配的坐标边界
1033+
* @return 适配边界的相机位置,如果地图已销毁则返回 null
1034+
*/
1035+
public getCameraForBounds(bounds: LatLngBounds): CameraPosition | null {
1036+
if (this.destroyed) {
1037+
return null;
1038+
}
1039+
// 获取当前相机位置以使用当前的 bearing 和 tilt
1040+
const currentCamera = this.getCameraPosition();
1041+
const bearing = currentCamera ? currentCamera.bearing : 0;
1042+
const tilt = currentCamera ? currentCamera.tilt : 0;
1043+
1044+
// 使用零 padding
1045+
return this.getCameraForLatLngBounds(bounds, 0, 0, 0, 0, bearing, tilt);
1046+
}
1047+
1048+
/**
1049+
* 获取适配指定坐标边界和内边距的相机位置(使用当前 bearing 和 tilt)
1050+
* 参考 Android 的 getCameraForLatLngBounds(LatLngBounds, int[] padding) 方法
1051+
*
1052+
* @param bounds 要适配的坐标边界
1053+
* @param padding 内边距(EdgeInsets 对象)
1054+
* @return 适配边界和内边距的相机位置,如果地图已销毁则返回 null
1055+
*/
1056+
public getCameraForBoundsWithPadding(bounds: LatLngBounds, padding: EdgeInsets): CameraPosition | null {
1057+
if (this.destroyed) {
1058+
return null;
1059+
}
1060+
// 获取当前相机位置以使用当前的 bearing 和 tilt
1061+
const currentCamera = this.getCameraPosition();
1062+
const bearing = currentCamera ? currentCamera.bearing : 0;
1063+
const tilt = currentCamera ? currentCamera.tilt : 0;
1064+
1065+
// 将 EdgeInsets 解构为独立的 padding 参数
1066+
return this.getCameraForLatLngBounds(bounds, padding.top, padding.left, padding.bottom, padding.right, bearing, tilt);
1067+
}
1068+
1069+
/**
1070+
* 获取适配指定坐标边界、内边距、bearing 和 tilt 的相机位置
1071+
* 参考 Android 的 getCameraForLatLngBounds(LatLngBounds, int[] padding, double bearing, double tilt) 方法
1072+
*
1073+
* @param bounds 要适配的坐标边界
1074+
* @param padding 内边距(EdgeInsets 对象)
1075+
* @param bearing 方位角(0-360度,0表示正北)
1076+
* @param tilt 倾斜角(0-60度,0表示垂直俯视)
1077+
* @return 适配所有参数的相机位置,如果地图已销毁则返回 null
1078+
*/
1079+
public getCameraForBoundsWithOptions(bounds: LatLngBounds, padding: EdgeInsets, bearing: number, tilt: number): CameraPosition | null {
1080+
if (this.destroyed) {
1081+
return null;
1082+
}
1083+
// 将 EdgeInsets 解构为独立的 padding 参数
1084+
return this.getCameraForLatLngBounds(bounds, padding.top, padding.left, padding.bottom, padding.right, bearing, tilt);
1085+
}
1086+
1087+
/**
1088+
* 设置相机以适配指定的坐标边界
1089+
* 参考 iOS 的 setVisibleCoordinateBounds:animated: 方法
1090+
*
1091+
* @param bounds 要适配的坐标边界
1092+
* @param animated 是否使用动画,默认 true
1093+
* @param duration 动画持续时间(毫秒),默认 300ms
1094+
*/
1095+
public setCameraForBounds(bounds: LatLngBounds, animated: boolean = true, duration: number = 300): void {
1096+
if (this.destroyed) {
1097+
return;
1098+
}
1099+
1100+
const camera = this.getCameraForBounds(bounds);
1101+
if (!camera) {
1102+
return;
1103+
}
1104+
1105+
if (animated) {
1106+
this.animateCamera(camera, duration);
1107+
} else {
1108+
this.jumpTo(camera.bearing, camera.target.latitude, camera.target.longitude, camera.tilt, camera.zoom);
1109+
}
1110+
}
1111+
1112+
/**
1113+
* 设置相机以适配指定的坐标边界和内边距
1114+
* 参考 iOS 的 setVisibleCoordinateBounds:edgePadding:animated: 方法
1115+
*
1116+
* @param bounds 要适配的坐标边界
1117+
* @param padding 内边距(EdgeInsets 对象)
1118+
* @param animated 是否使用动画,默认 true
1119+
* @param duration 动画持续时间(毫秒),默认 300ms
1120+
*/
1121+
public setCameraForBoundsWithPadding(bounds: LatLngBounds, padding: EdgeInsets, animated: boolean = true, duration: number = 300): void {
1122+
if (this.destroyed) {
1123+
return;
1124+
}
1125+
1126+
const camera = this.getCameraForBoundsWithPadding(bounds, padding);
1127+
if (!camera) {
1128+
return;
1129+
}
1130+
1131+
if (animated) {
1132+
this.animateCamera(camera, duration);
1133+
} else {
1134+
this.jumpTo(camera.bearing, camera.target.latitude, camera.target.longitude, camera.tilt, camera.zoom);
1135+
}
1136+
}
1137+
1138+
/**
1139+
* 设置相机以适配指定的坐标边界、内边距、bearing 和 tilt
1140+
*
1141+
* @param bounds 要适配的坐标边界
1142+
* @param padding 内边距(EdgeInsets 对象)
1143+
* @param bearing 方位角(0-360度,0表示正北)
1144+
* @param tilt 倾斜角(0-60度,0表示垂直俯视)
1145+
* @param animated 是否使用动画,默认 true
1146+
* @param duration 动画持续时间(毫秒),默认 300ms
1147+
*/
1148+
public setCameraForBoundsWithOptions(bounds: LatLngBounds, padding: EdgeInsets, bearing: number, tilt: number, animated: boolean = true, duration: number = 300): void {
1149+
if (this.destroyed) {
1150+
return;
1151+
}
1152+
1153+
const camera = this.getCameraForBoundsWithOptions(bounds, padding, bearing, tilt);
1154+
if (!camera) {
1155+
return;
1156+
}
1157+
1158+
if (animated) {
1159+
this.animateCamera(camera, duration);
1160+
} else {
1161+
this.jumpTo(camera.bearing, camera.target.latitude, camera.target.longitude, camera.tilt, camera.zoom);
1162+
}
1163+
}
1164+
10281165
public getCameraForGeometry(geometry: ESObject, top: number, left: number, bottom: number, right: number,
10291166
bearing?: number, tilt?: number): CameraPosition | null {
10301167
if (this.destroyed) {
@@ -1214,6 +1351,46 @@ export class MapLibreMap {
12141351
return result as CameraPosition;
12151352
}
12161353

1354+
/**
1355+
* 设置相机位置(立即跳转,无动画)
1356+
* 参考 Android 的简化相机设置方法
1357+
*
1358+
* @param position 目标相机位置
1359+
*/
1360+
public setCameraPosition(position: CameraPosition): void;
1361+
1362+
/**
1363+
* 设置相机位置(带动画)
1364+
* 参考 Android 的简化相机设置方法
1365+
*
1366+
* @param position 目标相机位置
1367+
* @param duration 动画持续时间(毫秒)
1368+
*/
1369+
public setCameraPosition(position: CameraPosition, duration: number): void;
1370+
1371+
/**
1372+
* setCameraPosition 实现
1373+
*/
1374+
public setCameraPosition(position: CameraPosition, duration?: number): void {
1375+
if (this.destroyed) {
1376+
return;
1377+
}
1378+
1379+
if (duration !== undefined && duration > 0) {
1380+
// 使用动画
1381+
this.animateCamera(position, duration);
1382+
} else {
1383+
// 立即跳转
1384+
this.jumpTo(
1385+
position.bearing,
1386+
position.target.latitude,
1387+
position.target.longitude,
1388+
position.tilt,
1389+
position.zoom
1390+
);
1391+
}
1392+
}
1393+
12171394
// 注解
12181395
/**
12191396
* 更新 Marker

0 commit comments

Comments
 (0)