Android:如何从Firebase实时数据库查询设备附近位置?
解决Firebase实时数据库附近位置查询的两种方案
嘿,我懂你现在的处境——700多个带经纬度的Gym对象,想只展示设备附近的内容,GeoFire上手有点懵对吧?别慌,我给你两种实用方案,一步步来:
一、用GeoFire(官方推荐,高效靠谱)
其实GeoFire的核心逻辑就是把你的数据Key和经纬度绑定存储,然后通过它的API查询指定半径内的Key,再去获取对应的完整数据。我帮你把步骤拆得明明白白:
1. 先集成GeoFire
在你的Module级别build.gradle里添加依赖:
implementation 'com.firebase:geofire-android:3.1.0'
2. 确保数据结构兼容
GeoFire需要单独的节点存储位置映射(和你的Gym节点分开),比如你的Gym数据在/gyms下,那GeoFire的位置数据可以存在/gyms_locations下。如果之前没存位置映射,你可以批量导入或者在新增Gym时同时写入:
// 新增Gym时同步写入GeoFire位置 DatabaseReference gymsRef = FirebaseDatabase.getInstance().getReference("gyms"); String gymKey = gymsRef.push().getKey(); // 生成新的Gym唯一Key Gym newGym = new Gym(...); // 初始化你的Gym对象 gymsRef.child(gymKey).setValue(newGym); // 写入GeoFire位置映射 DatabaseReference geoFireRef = FirebaseDatabase.getInstance().getReference("gyms_locations"); GeoFire geoFire = new GeoFire(geoFireRef); geoFire.setLocation(gymKey, new GeoLocation( Double.parseDouble(newGym.getLatitude()), Double.parseDouble(newGym.getLongitude()) ));
3. 查询设备附近的Gym
先获取设备当前经纬度,然后用GeoFire查询指定半径内的Gym Key,再拉取对应数据并添加Marker:
// 假设你已经获取到设备当前经纬度:currentLat, currentLng DatabaseReference geoFireRef = FirebaseDatabase.getInstance().getReference("gyms_locations"); GeoFire geoFire = new GeoFire(geoFireRef); // 设置查询半径,比如5公里(单位:千米) GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(currentLat, currentLng), 5.0); geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() { @Override public void onKeyEntered(String key, GeoLocation location) { // 拉取对应Key的Gym完整数据 DatabaseReference gymRef = FirebaseDatabase.getInstance().getReference("gyms").child(key); gymRef.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { Gym gym = dataSnapshot.getValue(Gym.class); gym.setGym_id(dataSnapshot.getKey()); // 复用你原来的Marker添加逻辑 LatLng newLocation = new LatLng( Double.parseDouble(gym.getLatitude()), Double.parseDouble(gym.getLongitude()) ); MarkerOptions markerOptions = new MarkerOptions() .position(newLocation) .title(gym.getName()) .snippet(gym.getUrl()); if (dataSnapshot.hasChild("raid")) { markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bMapRaid)); } else { markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bMap)); } Marker marker = mMap.addMarker(markerOptions); marker.setTag(gym); } @Override public void onCancelled(DatabaseError databaseError) { Log.e(TAG, "获取Gym数据失败", databaseError.toException()); } }); } @Override public void onKeyExited(String key) { // 当Gym离开查询范围时,可根据Key移除对应Marker(建议用Map存储Marker与Key的关联) } @Override public void onKeyMoved(String key, GeoLocation location) { // 当Gym位置更新时,可更新对应Marker的位置 } @Override public void onGeoQueryReady() { Log.d(TAG, "附近Gym初始查询完成"); } @Override public void onGeoQueryError(DatabaseError error) { Log.e(TAG, "GeoQuery出错", error.toException()); } });
二、手动计算距离(适合快速验证,数据量不大时可用)
如果暂时不想集成GeoFire,你可以先获取设备当前位置,遍历所有Gym并计算距离,过滤出符合条件的内容。700条数据的话性能尚可,但数据量持续增长后,效率不如GeoFire:
1. 添加经纬度距离计算方法
// 计算两个经纬度之间的距离(单位:千米) private double calculateDistance(double lat1, double lon1, double lat2, double lon2) { final int R = 6371; // 地球半径(千米) double latDistance = Math.toRadians(lat2 - lat1); double lonDistance = Math.toRadians(lon2 - lon1); double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; }
2. 修改原有监听代码,添加距离过滤
// 假设你已经获取到设备当前经纬度:currentLat, currentLng ChildEventListener childEventListener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey()); Gym gym = dataSnapshot.getValue(Gym.class); gym.setGym_id(dataSnapshot.getKey()); // 计算当前Gym与设备的距离 double gymLat = Double.parseDouble(gym.getLatitude()); double gymLng = Double.parseDouble(gym.getLongitude()); double distance = calculateDistance(currentLat, currentLng, gymLat, gymLng); // 只显示5公里以内的Gym if (distance <= 5.0) { LatLng newLocation = new LatLng(gymLat, gymLng); MarkerOptions markerOptions = new MarkerOptions() .position(newLocation) .title(gym.getName()) .snippet(gym.getUrl()); if (dataSnapshot.hasChild("raid")) { markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bMapRaid)); } else { markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bMap)); } Marker marker = mMap.addMarker(markerOptions); marker.setTag(gym); } } // 按需实现onChildChanged、onChildRemoved等其他回调方法 }; mDatabase = FirebaseDatabase.getInstance().getReference("gyms"); mDatabase.addChildEventListener(childEventListener);
小提示
- 使用GeoFire时,记得给
/gyms_locations节点配置合适的安全规则,确保只有授权用户能访问。 - 手动计算距离的方式适合快速验证,但数据量增大后,GeoFire的索引优化会带来更明显的性能优势。
内容的提问来源于stack exchange,提问作者Jordi Juan
相关产品推荐
相关产品推荐

