#gjl # Create your views here. import datetime import itertools import json import logging from datetime import timezone from itertools import groupby import django import requests from django.contrib.auth.decorators import login_required from django.http import HttpResponse from django.http import JsonResponse from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt # 导入当前应用的模型,用于在视图中操作数据库 from .models import OwnTrackLog # 获取一个 logger 实例,用于记录日志信息(如请求、错误等) logger = logging.getLogger(__name__) # 视图函数:接收并处理 OwnTracks 客户端上传的位置日志数据 # 使用 @csrf_exempt 装饰器免除 CSRF 验证,因为客户端可能无法提供 CSRF token # 该函数预期接收 JSON 格式的 POST 请求 @csrf_exempt def manage_owntrack_log(request): try: # gjl 读取请求体的原始数据,并解码为 UTF-8 字符串,然后解析为 Python 字典 s = json.loads(request.read().decode('utf-8')) # gjl 从解析后的数据中提取 tid(用户/设备标识)、lat(纬度)、lon(经度) tid = s['tid'] lat = s['lat'] lon = s['lon'] # gjl 记录一条 INFO 级别的日志,包含接收到的位置信息 logger.info( 'tid:{tid}.lat:{lat}.lon:{lon}'.format( tid=tid, lat=lat, lon=lon)) # 验证提取的数据是否都存在(非空) if tid and lat and lon: # gjl 创建一个新的 OwnTrackLog 模型实例 m = OwnTrackLog() # gjl 将接收到的数据赋值给模型字段 m.tid = tid m.lat = lat m.lon = lon # gjl 保存到数据库(creation_time 会自动使用默认值 now) m.save() # gjl 返回 HTTP 响应 'ok',表示数据接收和保存成功 return HttpResponse('ok') else: # gjl 如果数据不完整,返回 'data error' return HttpResponse('data error') except Exception as e: # gjl 捕获任何异常,记录错误日志,并返回 'error' 响应 logger.error(e) return HttpResponse('error') # gjl 视图函数:渲染显示地图的 HTML 页面 # gjl 使用 @login_required 装饰器,确保只有登录用户才能访问 @login_required def show_maps(request): # gjl 进一步检查用户是否为超级用户(管理员) if request.user.is_superuser: # gjl 获取当前 UTC 日期,作为默认日期 defaultdate = str(datetime.datetime.now(timezone.utc).date()) # gjl 从 GET 请求参数中获取 'date',如果不存在则使用默认日期 date = request.GET.get('date', defaultdate) # gjl 构造模板上下文,将日期传递给前端 context = { 'date': date } # gjl 使用指定的 HTML 模板渲染响应 return render(request, 'owntracks/show_maps.html', context) else: # gjl 如果不是超级用户,返回 403 禁止访问 from django.http import HttpResponseForbidden return HttpResponseForbidden() # gjl 视图函数:渲染显示有轨迹记录的日期列表页面 # gjl 使用 @login_required 装饰器,确保只有登录用户才能访问 @login_required def show_log_dates(request): # gjl 查询所有 OwnTrackLog 记录的 creation_time 字段值 dates = OwnTrackLog.objects.values_list('creation_time', flat=True) # gjl 将 datetime 对象转换为 'YYYY-MM-DD' 格式的字符串,并去重、排序 results = list(sorted(set(map(lambda x: x.strftime('%Y-%m-%d'), dates)))) # gjl 构造模板上下文,将日期列表传递给前端 context = { 'results': results } # gjl 使用指定的 HTML 模板渲染响应 return render(request, 'owntracks/show_log_dates.html', context) # gjl 辅助函数:将 GPS 坐标(WGS-84)批量转换为高德地图坐标系(GCJ-02) # gjl 由于高德 API 有数量限制,此处每次处理最多 30 个点 # gjl 注意:此函数在 get_datas 中被注释掉,当前未使用 def convert_to_amap(locations): convert_result = [] # 存储转换后的坐标结果 it = iter(locations) # 创建一个迭代器 # gjl 每次从迭代器中取出最多 30 个位置点 item = list(itertools.islice(it, 30)) while item: # gjl 将每个点的经度和纬度拼接成 "lon,lat" 字符串,用分号连接成一个列表 datas = ';'.join( set(map(lambda x: str(x.lon) + ',' + str(x.lat), item))) # gjl 高德坐标转换 API 的固定参数 key = '8440a376dfc9743d8924bf0ad141f28e' api = 'http://restapi.amap.com/v3/assistant/coordinate/convert' # 构造请求参数 query = { 'key': key, 'locations': datas, 'coordsys': 'gps' # 指定原始坐标系为 GPS(WGS-84) } # 发送 GET 请求到高德 API rsp = requests.get(url=api, params=query) # 解析 API 返回的 JSON 数据 result = json.loads(rsp.text) # 如果返回结果包含转换后的坐标,则添加到结果列表 if "locations" in result: convert_result.append(result['locations']) # 继续处理下一批最多 30 个点 item = list(itertools.islice(it, 30)) # 将所有批次的转换结果合并成一个以分号分隔的字符串并返回 return ";".join(convert_result) # gjl 视图函数:返回指定日期的位置轨迹数据,供前端地图使用 # gjl 返回 JSON 格式的数据,通常由 AJAX 调用 # gjl 使用 @login_required 装饰器,确保只有登录用户才能访问 @login_required def get_datas(request): # 获取当前 UTC 时间 now = django.utils.timezone.now().replace(tzinfo=timezone.utc) # 构造查询日期的起始时间(当天 00:00:00 UTC) querydate = django.utils.timezone.datetime( now.year, now.month, now.day, 0, 0, 0) # 如果请求中包含 'date' 参数,则使用该参数指定的日期 if request.GET.get('date', None): # 解析 'YYYY-MM-DD' 格式的日期字符串 date = list(map(lambda x: int(x), request.GET.get('date').split('-'))) querydate = django.utils.timezone.datetime( date[0], date[1], date[2], 0, 0, 0) # gjl 计算查询结束时间(起始时间 + 1 天) nextdate = querydate + datetime.timedelta(days=1) # gjl查询指定日期范围内(当天)的所有位置记录 models = OwnTrackLog.objects.filter( creation_time__range=(querydate, nextdate)) # gjl 初始化结果列表 result = list() # glj 如果查询到记录,则按 tid(用户/设备)分组处理 if models and len(models): for tid, item in groupby( # 先按 tid 排序,以便 groupby 正确分组 sorted(models, key=lambda k: k.tid), # 指定分组依据为 tid key=lambda k: k.tid): # gjl 为每个 tid 创建一个数据字典 d = dict() d["name"] = tid # 记录设备/用户名称 paths = list() # 存储该设备的轨迹点序列 # gjl 方案一(当前启用):直接使用原始 GPS 坐标 # gjl 遍历该设备当天的所有位置点,按时间排序 for location in sorted(item, key=lambda x: x.creation_time): # gjl 将每个点的经度和纬度作为字符串列表添加到 paths paths.append([str(location.lon), str(location.lat)]) # # gjl 方案二(注释中):使用高德转换后的坐标 # # gjl 先转换坐标,然后解析 # locations = convert_to_amap( # sorted(item, key=lambda x: x.creation_time)) # for i in locations.split(';'): # paths.append(i.split(',')) # 将轨迹点列表赋值给字典的 "path" 键 d["path"] = paths # 将该设备的轨迹数据添加到总结果列表 result.append(d) # gjl 将结果列表序列化为 JSON 并返回 # gjl safe=False 允许返回非字典类型的对象(如列表) return JsonResponse(result, safe=False)