当前位置: 首页 > news >正文

面向GIS的Android studio移动开发(二)--在地图上绘制电子围栏

电子围栏,校园跑的常客,也是定位打卡必不可少的东西

主要代码:

创建电子围栏代码

// 添加多边形地理围栏(兼容2023版SDK)private void addPolygon(String fenceName, List<LatLng> points) {if (points == null || points.size() < 3) {Toast.makeText(this, "多边形至少需要3个点", Toast.LENGTH_SHORT).show();return;}// 生成唯一围栏IDString fenceId = "polygon_" + mCustomID++;// 转换坐标点格式List<DPoint> dPoints = new ArrayList<>();for (LatLng point : points) {dPoints.add(new DPoint(point.latitude, point.longitude));}// 正确调用方式(关键修改点)mClientInAndStayAction.addGeoFence(dPoints,               // 参数1: 多边形顶点坐标(必须闭合)fenceId                // 参数2: 自定义围栏ID);// 绘制多边形到地图PolygonOptions options = new PolygonOptions().addAll(points).fillColor(0x5566CCFF)    // 填充色(带透明度).strokeColor(Color.BLUE)  // 边框色.strokeWidth(5);         // 边框宽度(px)Polygon polygon = aMap.addPolygon(options);mCustomEntitys.put(fenceId, polygon);// 设置围栏监听(新增)mClientInAndStayAction.setGeoFenceListener(new GeoFenceListener() {@Overridepublic void onGeoFenceCreateFinished(List<GeoFence> geoFences, int errorCode, String msg) {if (errorCode == GeoFence.ADDGEOFENCE_SUCCESS) {runOnUiThread(() ->Toast.makeText(MapActivity.this, fenceName + "围栏创建成功", Toast.LENGTH_SHORT).show());}}});// 移动视角到围栏中心aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(points.get(0), 15));}//这里通过数据库进行添加电子围栏的多边形private void addTiananmenFence() {List<LatLng> tiananmenArea = new ArrayList<>();// 添加闭合坐标点(示例数据)tiananmenArea.add(new LatLng(39.908722, 116.397499)); // 天安门中心tiananmenArea.add(new LatLng(39.910000, 116.395000)); // 西北角tiananmenArea.add(new LatLng(39.909500, 116.400000));  // 东北角tiananmenArea.add(tiananmenArea.get(0)); // 闭合多边形addPolygon("天安门核心区", tiananmenArea);}

创建广播(当定位在定位外时触发)

// 修改onStart方法中的注册逻辑@Overrideprotected void onStart() {super.onStart();// 创建带安全标志的广播过滤器IntentFilter filter = new IntentFilter(GEOFENCE_BROADCAST_ACTION);// 适配Android 13+广播权限要求if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {registerReceiver(mGeoFenceReceiver,filter,Context.RECEIVER_NOT_EXPORTED // 禁止外部应用访问);} else {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {registerReceiver(mGeoFenceReceiver, filter, Context.RECEIVER_NOT_EXPORTED);}}createNotificationChannel();}@Overrideprotected void onStop() {super.onStop();// 取消注册接收器unregisterReceiver(mGeoFenceReceiver);}private void createNotificationChannel() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {CharSequence name = "地理围栏警报";String description = "电子围栏越界通知";int importance = NotificationManager.IMPORTANCE_HIGH;NotificationChannel channel = new NotificationChannel(ALERT_CHANNEL_ID, name, importance);channel.setDescription(description);NotificationManager notificationManager = getSystemService(NotificationManager.class);notificationManager.createNotificationChannel(channel);}}

发送日志

 // 在MapActivity中private final BroadcastReceiver mGeoFenceReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if (!isValidBroadcast(intent)) return;Bundle bundle = intent.getExtras();if (bundle == null) return;// 解析基础数据int status = bundle.getInt(GeoFence.BUNDLE_KEY_FENCESTATUS);String fenceId = bundle.getString(GeoFence.BUNDLE_KEY_FENCEID);GeoFence fence = bundle.getParcelable(GeoFence.BUNDLE_KEY_FENCE);// 分发事件处理handleGeoFenceEvent(context, status, fenceId, fence);}private boolean isValidBroadcast(Intent intent) {return intent != null &&GEOFENCE_BROADCAST_ACTION.equals(intent.getAction());}private void handleGeoFenceEvent(Context context, int status,String fenceId, GeoFence fence) {switch (status) {case GeoFence.STATUS_IN:handleEnter(context, fenceId);break;case GeoFence.STATUS_OUT:handleExit(context, fence);break;case GeoFence.STATUS_STAYED:handleStay(fence);break;}}private void handleEnter(Context context, String fenceId) {// 记录日志GeoFenceLogger.logEnter(fenceId);// UI提示Toast.makeText(context, "进入监控区域", Toast.LENGTH_SHORT).show();}private void handleExit(Context context, GeoFence fence) {if (fence == null) return;// 记录日志GeoFenceLogger.logExit(fence.getFenceId());// 触发警报String alertMsg = "⚠️ 已离开安全区域: " + fence.getFenceId();Toast.makeText(context, alertMsg, Toast.LENGTH_LONG).show();sendAlertNotification(context, alertMsg);}private void handleStay(GeoFence fence) {if (fence == null) return;// 记录日志GeoFenceLogger.logStay(fence.getFenceId(), fence.getExpiration());}};private void sendAlertNotification(Context context, String message) {NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);// 构建通知Notification notification = new NotificationCompat.Builder(context, ALERT_CHANNEL_ID).setSmallIcon(R.drawable.ic_alert).setContentTitle("区域越界警告").setContentText(message).setPriority(NotificationCompat.PRIORITY_MAX).setAutoCancel(true).build();// 显示通知if (manager != null) {manager.notify(new Random().nextInt(1000), notification);}}
package com.example.gdmap;import android.util.Log;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;//用来记录学生何时进出入电子围栏
//这部分可将log打印的日志部分写为string放在教师端的文本框中
// GeoFenceLogger.java
public class GeoFenceLogger {private static final String TAG = "GeoFenceTracker";private static final SimpleDateFormat DATE_FORMAT =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault());// 记录进入事件public static void logEnter(String fenceId) {String timestamp = DATE_FORMAT.format(new Date());Log.d(TAG, String.format("[进入] 围栏ID: %s | 时间: %s", fenceId, timestamp));}// 记录离开事件public static void logExit(String fenceId) {String timestamp = DATE_FORMAT.format(new Date());Log.d(TAG, String.format("[离开] 围栏ID: %s | 时间: %s", fenceId, timestamp));}// 记录停留事件public static void logStay(String fenceId, long duration) {String timestamp = DATE_FORMAT.format(new Date());Log.d(TAG, String.format("[停留] 围栏ID: %s | 时间: %s | 持续时间: %dms",fenceId, timestamp, duration));}
}

activity代码

package com.example.gdmap;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;import com.amap.api.fence.GeoFence;
import com.amap.api.fence.GeoFenceListener;
import com.amap.api.maps.AMap;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.MapView;
import com.amap.api.maps.MapsInitializer;
import com.amap.api.maps.model.CircleOptions;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.Marker;
import com.amap.api.maps.model.MarkerOptions;
import com.amap.api.maps.model.MyLocationStyle;
import com.amap.api.fence.GeoFence;
import com.amap.api.fence.GeoFenceClient;
import com.amap.api.maps.model.Polygon;
import com.amap.api.maps.model.PolygonOptions;
import com.amap.api.maps.model.Circle;
import com.amap.api.location.DPoint; // 注意:高德使用DPoint而非LatLng
import android.Manifest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;public class MapActivity extends AppCompatActivity implementsAMap.OnMarkerClickListener,AMap.InfoWindowAdapter {private MapView mMapView;// 地理围栏相关private GeoFenceClient mClientInAndStayAction;private int mCustomID = 1000; // 围栏ID种子值private HashMap<String, Object> mCustomEntitys = new HashMap<>(); // 存储围栏图形对象private static final String GEOFENCE_BROADCAST_ACTION = "com.example.gdmap.geofence";private AMap aMap;private static final int PERMISSION_REQUEST_CODE = 1001;// 在MapActivity类中添加以下代码private static final String TAG = "GeoFenceTracker";// 定义通知渠道IDprivate static final String ALERT_CHANNEL_ID = "geo_fence_alerts";private static final int REQUEST_CODE = 1001;private void requestLocationPermission() {if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},REQUEST_CODE);}}@Overridepublic void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 关键调用if (requestCode == REQUEST_CODE) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {initializeMap();} else {Toast.makeText(this, "权限被拒绝,部分功能受限", Toast.LENGTH_SHORT).show();}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_map);// 初始化地图MapsInitializer.updatePrivacyShow(this, true, true);MapsInitializer.updatePrivacyAgree(this, true);mMapView = findViewById(R.id.map);mMapView.onCreate(savedInstanceState);initializeMap();setupLocationStyle();addMarkers();addTiananmenFence();}private void initializeMap() {if (aMap == null) {aMap = mMapView.getMap();mClientInAndStayAction = new GeoFenceClient(getApplicationContext());mClientInAndStayAction.createPendingIntent(GEOFENCE_BROADCAST_ACTION);mClientInAndStayAction.setActivateAction(GeoFenceClient.GEOFENCE_IN | GeoFenceClient.GEOFENCE_STAYED);// 设置信息窗口适配器aMap.setInfoWindowAdapter(this);// 设置Marker点击监听器aMap.setOnMarkerClickListener(this);// 初始地图位置和缩放级别aMap.animateCamera(CameraUpdateFactory.zoomTo(16));aMap.getUiSettings().setScaleControlsEnabled(true);}}private void setupLocationStyle() {MyLocationStyle myLocationStyle = new MyLocationStyle();myLocationStyle.interval(6000);myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_SHOW);aMap.setMyLocationStyle(myLocationStyle);aMap.setMyLocationEnabled(true);}//这里可以通过数据库进行添加点private void addMarkers() {// 使用自定义方法创建带数据的MarkeraddMarker(28.711666, 115.826600, "AAA水果批发商小王", "苹果");addMarker(28.711667, 115.826750, "AAA水果批发商小李", "芒果");addMarker(28.711680, 115.826450, "AAA水果批发商小张", "草莓");// 东北地区addMarker(45.8038, 126.5340, "哈尔滨水果批发商老刘", "蓝莓"); // 黑龙江哈尔滨addMarker(43.8171, 125.3235, "长春水果批发商孙姐", "人参果"); // 吉林长春addMarker(41.8354, 123.4299, "沈阳水果批发商老金", "南果梨"); // 辽宁沈阳// 华北地区addMarker(39.9042, 116.4074, "北京新发地批发市场", "平谷大桃"); // 北京addMarker(37.8706, 112.5489, "太原水果批发商老陈", "沙金红杏"); // 山西太原addMarker(38.0428, 114.5149, "石家庄水果批发商赵总", "赞皇大枣"); // 河北石家庄// 华东地区addMarker(31.2304, 121.4737, "上海西郊国际批发", "南汇水蜜桃"); // 上海addMarker(30.2595, 120.2194, "杭州水果批发商王姐", "塘栖枇杷"); // 浙江杭州addMarker(32.0603, 118.7969, "南京众彩批发市场", "固城湖螃蟹"); // 江苏南京addMarker(36.6512, 117.1201, "济南堤口批发市场", "烟台樱桃"); // 山东济南// 华中地区addMarker(30.5951, 114.2999, "武汉光霞果批市场", "梁子湖螃蟹"); // 湖北武汉addMarker(28.1941, 112.9723, "长沙红星批发市场", "炎陵黄桃"); // 湖南长沙addMarker(34.7473, 113.6253, "郑州万邦物流城", "灵宝苹果"); // 河南郑州// 华南地区addMarker(23.1291, 113.2644, "广州江南果菜市场", "增城荔枝"); // 广东广州addMarker(20.0444, 110.1999, "海口南北水果市场", "文昌椰子"); // 海南海口addMarker(22.8176, 108.3663, "南宁海吉星市场", "百色芒果"); // 广西南宁// 西北地区addMarker(34.3416, 108.9398, "西安雨润批发市场", "周至猕猴桃"); // 陕西西安addMarker(36.0611, 103.8343, "兰州大青山市场", "白兰瓜"); // 甘肃兰州addMarker(43.8256, 87.6168, "乌鲁木齐九鼎市场", "哈密瓜"); // 新疆乌鲁木齐// 西南地区addMarker(29.6535, 91.1705, "拉萨药王山市场", "林芝苹果"); // 西藏拉萨addMarker(25.0433, 102.7062, "昆明金马正昌市场", "蒙自石榴"); // 云南昆明addMarker(26.6470, 106.6302, "贵阳石板哨市场", "修文猕猴桃"); // 贵州贵阳addMarker(30.5728, 104.0668, "成都濛阳农批市场", "攀枝花芒果"); // 四川成都// 特别行政区addMarker(22.3193, 114.1694, "香港长沙湾市场", "菲律宾香蕉"); // 香港}private void addMarker(double lat, double lng, String title, String phone) {LatLng position = new LatLng(lat, lng);aMap.addMarker(new MarkerOptions().position(position).title(title).snippet("售卖商品: " + phone).draggable(false));}// 实现信息窗口布局@Overridepublic View getInfoWindow(Marker marker) {View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_window, null);renderInfoWindow(marker, infoWindow);return infoWindow;}@Overridepublic View getInfoContents(Marker marker) {return null; // 使用默认信息窗口背景时返回null}private void renderInfoWindow(Marker marker, View view) {TextView title = view.findViewById(R.id.info_window_title);TextView content = view.findViewById(R.id.info_window_content);title.setText(marker.getTitle());content.setText(marker.getSnippet());}// 处理Marker点击事件@Overridepublic boolean onMarkerClick(Marker marker) {// 显示信息窗口(第二个参数为是否强制使用默认布局)marker.showInfoWindow();return true; // 消费点击事件}// 添加多边形地理围栏(兼容2023版SDK)private void addPolygon(String fenceName, List<LatLng> points) {if (points == null || points.size() < 3) {Toast.makeText(this, "多边形至少需要3个点", Toast.LENGTH_SHORT).show();return;}// 生成唯一围栏IDString fenceId = "polygon_" + mCustomID++;// 转换坐标点格式List<DPoint> dPoints = new ArrayList<>();for (LatLng point : points) {dPoints.add(new DPoint(point.latitude, point.longitude));}// 正确调用方式(关键修改点)mClientInAndStayAction.addGeoFence(dPoints,               // 参数1: 多边形顶点坐标(必须闭合)fenceId                // 参数2: 自定义围栏ID);// 绘制多边形到地图PolygonOptions options = new PolygonOptions().addAll(points).fillColor(0x5566CCFF)    // 填充色(带透明度).strokeColor(Color.BLUE)  // 边框色.strokeWidth(5);         // 边框宽度(px)Polygon polygon = aMap.addPolygon(options);mCustomEntitys.put(fenceId, polygon);// 设置围栏监听(新增)mClientInAndStayAction.setGeoFenceListener(new GeoFenceListener() {@Overridepublic void onGeoFenceCreateFinished(List<GeoFence> geoFences, int errorCode, String msg) {if (errorCode == GeoFence.ADDGEOFENCE_SUCCESS) {runOnUiThread(() ->Toast.makeText(MapActivity.this, fenceName + "围栏创建成功", Toast.LENGTH_SHORT).show());}}});// 移动视角到围栏中心aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(points.get(0), 15));}//这里通过数据库进行添加电子围栏的多边形private void addTiananmenFence() {List<LatLng> tiananmenArea = new ArrayList<>();// 添加闭合坐标点(示例数据)tiananmenArea.add(new LatLng(39.908722, 116.397499)); // 天安门中心tiananmenArea.add(new LatLng(39.910000, 116.395000)); // 西北角tiananmenArea.add(new LatLng(39.909500, 116.400000));  // 东北角tiananmenArea.add(tiananmenArea.get(0)); // 闭合多边形addPolygon("天安门核心区", tiananmenArea);}// 修改onStart方法中的注册逻辑@Overrideprotected void onStart() {super.onStart();// 创建带安全标志的广播过滤器IntentFilter filter = new IntentFilter(GEOFENCE_BROADCAST_ACTION);// 适配Android 13+广播权限要求if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {registerReceiver(mGeoFenceReceiver,filter,Context.RECEIVER_NOT_EXPORTED // 禁止外部应用访问);} else {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {registerReceiver(mGeoFenceReceiver, filter, Context.RECEIVER_NOT_EXPORTED);}}createNotificationChannel();}@Overrideprotected void onStop() {super.onStop();// 取消注册接收器unregisterReceiver(mGeoFenceReceiver);}private void createNotificationChannel() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {CharSequence name = "地理围栏警报";String description = "电子围栏越界通知";int importance = NotificationManager.IMPORTANCE_HIGH;NotificationChannel channel = new NotificationChannel(ALERT_CHANNEL_ID, name, importance);channel.setDescription(description);NotificationManager notificationManager = getSystemService(NotificationManager.class);notificationManager.createNotificationChannel(channel);}}// 在MapActivity中private final BroadcastReceiver mGeoFenceReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if (!isValidBroadcast(intent)) return;Bundle bundle = intent.getExtras();if (bundle == null) return;// 解析基础数据int status = bundle.getInt(GeoFence.BUNDLE_KEY_FENCESTATUS);String fenceId = bundle.getString(GeoFence.BUNDLE_KEY_FENCEID);GeoFence fence = bundle.getParcelable(GeoFence.BUNDLE_KEY_FENCE);// 分发事件处理handleGeoFenceEvent(context, status, fenceId, fence);}private boolean isValidBroadcast(Intent intent) {return intent != null &&GEOFENCE_BROADCAST_ACTION.equals(intent.getAction());}private void handleGeoFenceEvent(Context context, int status,String fenceId, GeoFence fence) {switch (status) {case GeoFence.STATUS_IN:handleEnter(context, fenceId);break;case GeoFence.STATUS_OUT:handleExit(context, fence);break;case GeoFence.STATUS_STAYED:handleStay(fence);break;}}private void handleEnter(Context context, String fenceId) {// 记录日志GeoFenceLogger.logEnter(fenceId);// UI提示Toast.makeText(context, "进入监控区域", Toast.LENGTH_SHORT).show();}private void handleExit(Context context, GeoFence fence) {if (fence == null) return;// 记录日志GeoFenceLogger.logExit(fence.getFenceId());// 触发警报String alertMsg = "⚠️ 已离开安全区域: " + fence.getFenceId();Toast.makeText(context, alertMsg, Toast.LENGTH_LONG).show();sendAlertNotification(context, alertMsg);}private void handleStay(GeoFence fence) {if (fence == null) return;// 记录日志GeoFenceLogger.logStay(fence.getFenceId(), fence.getExpiration());}};private void sendAlertNotification(Context context, String message) {NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);// 构建通知Notification notification = new NotificationCompat.Builder(context, ALERT_CHANNEL_ID).setSmallIcon(R.drawable.ic_alert).setContentTitle("区域越界警告").setContentText(message).setPriority(NotificationCompat.PRIORITY_MAX).setAutoCancel(true).build();// 显示通知if (manager != null) {manager.notify(new Random().nextInt(1000), notification);}}// 生命周期方法@Overrideprotected void onResume() {super.onResume();mMapView.onResume();}@Overrideprotected void onPause() {super.onPause();mMapView.onPause();}@Overrideprotected void onDestroy() {super.onDestroy();mMapView.onDestroy();}@Overrideprotected void onSaveInstanceState(Bundle outState) {super.onSaveInstanceState(outState);mMapView.onSaveInstanceState(outState);}
}

标志.XML

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportWidth="24"android:viewportHeight="24"><pathandroid:fillColor="#FF0000"android:pathData="M12,2L3,20h18L12,2zM13,16h-2v-2h2v2zm0-4h-2V8h2v4z"/>
</vector>

请添加图片描述

效果图:

请添加图片描述
请添加图片描述

相关文章:

  • Spring AI Alibaba集成阿里云百炼大模型
  • 【已经解决诸多问题】Mamba安装
  • 延时双删-争议与我的思路-001
  • Neo4j数据库
  • 有哪些GIF图片转换的开源工具
  • 07 负载均衡
  • Linux的MySQL头文件和找不到头文件问题解决
  • windows多版本Python共存(大合集)
  • 方案精读:104页DeepSeek金融银行核算流程场景部署建设方案【附全文阅读】
  • LeetCode 155. 最小栈:Java 双栈解法详解
  • LWIP的Socket接口
  • SmartETL函数式组件的设计与应用
  • 【时时三省】(C语言基础)数组习题
  • 前端三剑客之HTML
  • LLM大语言模型系列1-token
  • 【AWS入门】Amazon SageMaker简介
  • [原创工具] 小说写作软件
  • spark-配置yarn模式
  • 吴恩达机器学习(1)——机器学习算法分类
  • SpringBoot项目里面发起http请求的几种方法
  • 《缶翁的世界》首发:看吴昌硕王一亭等湖州籍书画家的影响
  • 商务部:对原产于美国、欧盟、台湾地区和日本的进口共聚聚甲醛征收反倾销税
  • 15年全程免费,内蒙古准格尔旗实现幼儿园到高中0学费
  • 多少Moreless:向世界展示现代中式家具的生活美学
  • 学者三年实地调查被判AI代笔,论文AI率检测如何避免“误伤”
  • 终于,俄罗斯和乌克兰谈上了