ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Django的公园定位系统实战:从数据模型到地图联动

基于Django的公园定位系统实战:从数据模型到地图联动 毕业设计里“公园定位系统”这种题目看起来简单真动手做的时候坑却不少。很多同学拿到这种题目第一反应就是“地图标记点”实际上需求方真正要的是一个能管理公园设施、记录实时位置、支持人员调度的平台。我基于Django完整实现过这套系统从数据模型、接口设计到地图联动都跑通了这篇就把整个设计思路、核心代码和踩坑过程都摊开来讲给正在做类似选题或者想用Django练手的朋友一个能直接参考的样本。这个项目适合谁如果你是用Django做毕设的学生、想熟悉WebGIS基础开发的程序员或者需要一套公园管理后台的开发者都可以顺着这篇把骨架搭起来。我会重点讲清楚数据库设计、定位上报接口、距离计算和前后端地图联动这几个关键环节最后附上部署排查经验。1. 项目概述与核心需求拆解1.1 公园定位系统到底是做什么的先别急着写代码要把需求掰开看。公园定位系统不是单纯在网页上显示一张地图它至少得覆盖两类用户的需求。游客端需要的是“我在哪”“附近有什么”“怎么去”比如找厕所、找停车场、找某个景点系统要把这些设施的位置直观标出来并根据游客当前位置计算距离。管理端需要的是“人在哪”“设施状态怎么样”巡护人员定时上报位置后台能看到巡逻轨迹设施损坏上报后能在图上标红方便派单维修。所以我最终拆解出来的核心功能模块有这几个公园基础信息管理名称、面积、简介、中心坐标设施分类与位置管理厕所、停车场、长椅、垃圾桶、景点等用户实时位置采集与历史轨迹记录基于当前位置的周边设施查询和距离计算不同角色权限控制普通游客、巡护员、管理员这些功能全部落在Django的项目里前后端加起来大概十余个页面和接口就能跑通整个流程。1.2 为什么选Django而不是Flask或Node我常被问这个问题。做这种业务系统我最先考虑的还是Django原因很实在自带Admin后台设施管理、用户管理、巡护记录这些后台页面几乎不用自己写开发速度翻倍ORM好用模型一定义迁移脚本自动生成不用手动拼SQL用户认证和权限体系是现成的游客、管理员、巡护员三种角色直接用Group和Permission控制Django REST FrameworkDRF写接口非常顺手前后端分离时省很多事如果你用Flask确实更轻但用户权限、Admin、ORM这些都要自己搭或者另外找库对于一个毕设或者中小型系统来说重复造轮子不划算。Node那边也不是不行只是Django的生态对“表单后台管理关系型数据”这种需求贴合度更高。记住一个原则选型不是追求技术热门而是看哪个框架能让你花最少的时间把业务落地。2. 系统设计与数据模型实现2.1 数据模型设计从一张公园表开始我设计模型的时候没有一上来就搞十几个表而是抓住核心实体逐步扩展。最重要的三张表是公园表、设施表和位置记录表。下面是我实际使用的models.py关键代码from django.db import models from django.contrib.auth.models import User class Park(models.Model): name models.CharField(公园名称, max_length100) address models.CharField(地址, max_length200, blankTrue) longitude models.DecimalField(经度, max_digits9, decimal_places6) latitude models.DecimalField(纬度, max_digits9, decimal_places6) description models.TextField(公园简介, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) def __str__(self): return self.name class Meta: db_table park verbose_name 公园 verbose_name_plural 公园 class Facility(models.Model): CATEGORY_CHOICES ( (restroom, 厕所), (parking, 停车场), (attraction, 景点), (bench, 长椅), (trash, 垃圾桶), ) park models.ForeignKey(Park, on_deletemodels.CASCADE, related_namefacilities) name models.CharField(设施名称, max_length100) category models.CharField(设施分类, max_length20, choicesCATEGORY_CHOICES) longitude models.DecimalField(经度, max_digits9, decimal_places6) latitude models.DecimalField(纬度, max_digits9, decimal_places6) status models.BooleanField(是否正常, defaultTrue) remark models.CharField(备注, max_length200, blankTrue) def __str__(self): return f{self.park.name}-{self.name} class Meta: db_table facility verbose_name 设施 verbose_name_plural 设施 class LocationRecord(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namelocation_records) longitude models.DecimalField(经度, max_digits9, decimal_places6) latitude models.DecimalField(纬度, max_digits9, decimal_places6) report_time models.DateTimeField(上报时间, auto_now_addTrue) def __str__(self): return f{self.user.username} ({self.report_time:%Y-%m-%d %H:%M:%S}) class Meta: db_table location_record verbose_name 位置记录 verbose_name_plural 位置记录这套模型看起来简单但足够支撑核心业务。你需要注意的是外键关系Facility 通过 ForeignKey 关联到 Park查询某个公园的设施时直接用 park.facilities.all()LocationRecord 关联到 User所以天然能知道是哪个人上报的位置位置表按时间批量查询就能画出轨迹不需要额外设计轨迹表2.2 坐标数据怎么存Decimal还是Float定位系统里最容易出错的就是经纬度字段类型。我知道很多人图省事直接写 FloatField但我建议用 DecimalField。为什么Float在计算过程中会丢失精度定位坐标即使差0.00001度在地图上也可能偏移一米以上。我实测过一个场景保存坐标时显示很准确但参与距离计算后结果跟地图测距总是差一点最后定位到字段精度问题。用 DecimalField 的时候记住两点max_digits 设置成9decimal_places设置成6足够存下经纬度的整数部分和小数部分如果从高德或百度坐标传递过来前端传什么就存什么不要自己在JS里反复取整有人会问要不要用 GeoDjango 的 PointField。如果你对空间数据库不熟或者部署环境不好装 PostGIS建议不要引入。用 DecimalField 在Python里算距离足够应付绝大多数毕设场景。2.3 Django自带Admin和权限体系的价值如果这个系统不用Admin我可能要多写十几二十个管理页面。公园表的增删改查、设施的分类管理、位置记录的查看筛选这些靠Django Admin就完成了。在admin.py里注册一下from django.contrib import admin from .models import Park, Facility, LocationRecord admin.register(Park) class ParkAdmin(admin.ModelAdmin): list_display (name, address, longitude, latitude) search_fields (name,) admin.register(Facility) class FacilityAdmin(admin.ModelAdmin): list_display (name, park, category, status) list_filter (category, status) search_fields (name, park__name) admin.register(LocationRecord) class LocationRecordAdmin(admin.ModelAdmin): list_display (user, longitude, latitude, report_time) list_filter (report_time,)权限控制我直接使用了Django自带的User和Group。创建三个组游客、巡护员、管理员。游客只有查看地图和查询设施的权限巡护员可以上报位置和查看自己的记录管理员拥有全部权限。在视图里用login_required和permission_required就能轻松限制接口访问不用自己手写session判断。这是我坚持用Django做这类系统的核心原因。3. 核心功能实现与编码过程3.1 前端方案模板渲染还是前后端分离在动手之前先定技术路线。我这次选的是 Django 模板 少量原生JavaScript而不是“Django只做API Vue独立前端”。原因是毕设或者中小型项目模板渲染的效率更高不用额外处理跨域问题也不用来回调试token认证。地图交互部分通过 Django 模板把公园坐标注入到一个全局变量里前端再调用地图SDK初始化。如果你一定要用前后端分离开发那我会建议配合Django REST Framework写API并在前端用Vue或React。但那样的话需要处理CORS、认证方式等一系列额外问题。我的建议是没有强需求就不要把架构搞复杂。下面是一段模板渲染的地图初始化代码!-- park_detail.html -- script var parkLng {{ park.longitude }}; var parkLat {{ park.latitude }}; initMap(parkLng, parkLat); /script这种写法最直接也不会出现接口跨域报错。3.2 实时位置上报接口怎么写游客端上报位置我提供的是一个简单的JSON接口。用Django原生视图写一个接口接收POST请求把经纬度写入位置记录表。import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required from .models import LocationRecord login_required csrf_exempt def report_location(request): if request.method ! POST: return JsonResponse({code: 405, msg: 仅支持POST请求}) try: data json.loads(request.body) longitude data.get(longitude) latitude data.get(latitude) if longitude is None or latitude is None: return JsonResponse({code: 400, msg: 缺少经纬度参数}) LocationRecord.objects.create( userrequest.user, longitudelongitude, latitudelatitude ) return JsonResponse({code: 0, msg: 上报成功}) except (TypeError, ValueError): return JsonResponse({code: 400, msg: 请求体格式错误})前端调用这个接口的时候我在JavaScript里用了浏览器的Geolocation API获取位置。function reportPosition() { if (!navigator.geolocation) { alert(当前浏览器不支持定位); return; } navigator.geolocation.getCurrentPosition(function (position) { var lng position.coords.longitude; var lat position.coords.latitude; fetch(/api/location/report/, { method: POST, headers: { Content-Type: application/json, X-CSRFToken: getCookie(csrftoken) }, body: JSON.stringify({ longitude: lng, latitude: lat }) }); }); }需要特别注意CSRF的问题。Django默认开启CSRF防护如果你用原生JS的fetch提交POST必须把csrftoken带上否则会一直返回403。还有一种做法是给接口加上csrf_exempt但生产环境不建议这么干我在demo里临时用真实部署时最好通过前端把token传过来。3.3 周边设施查询距离计算不做SQL硬算“附近1公里有哪些厕所”这种需求初学者最容易想成“在数据库里查所有设施然后逐个算距离”。小数据量当然没问题但如果公园里设施超过几万个这种查询会越来越慢。我用的是哈弗辛公式Haversine在Python里计算两点间球面距离import math def haversine_distance(lng1, lat1, lng2, lat2): R 6371.0 # 地球半径单位km lng1, lat1, lng2, lat2 map(float, [lng1, lat1, lng2, lat2]) phi1 math.radians(lat1) phi2 math.radians(lat2) delta_phi math.radians(lat2 - lat1) delta_lambda math.radians(lng2 - lng1) a math.sin(delta_phi / 2) ** 2 \ math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2 c 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) return R * c然后视图里这样查询附近设施def nearby_facilities(request, park_id): park Park.objects.get(pkpark_id) user_lng float(request.GET.get(lng, 0)) user_lat float(request.GET.get(lat, 0)) radius float(request.GET.get(radius, 1)) # 默认1公里 facilities [] for facility in park.facilities.all(): dist haversine_distance(user_lng, user_lat, float(facility.longitude), float(facility.latitude)) if dist radius: facilities.append({ id: facility.id, name: facility.name, category: facility.category, distance: round(dist * 1000, 1) # 转成米 }) facilities.sort(keylambda x: x[distance]) return JsonResponse({code: 0, data: facilities})这种实现虽然每次都遍历所有设施但对于公园这个量级完全够用。如果未来设施数量太大再考虑在数据库里用空间索引。你只需要理解一点代码里先把精度问题处理好性能优化永远要等数据量大到有真实瓶颈再说。3.4 地图展示接入第三方地图SDK公园定位系统最直观的展示还是地图。我选择的是高德地图JavaScript API原因一是国内访问稳定二是中文文档详细三是坐标体系适合国内地图展示。在页面里引入脚本并初始化地图link relstylesheet hrefhttps://cache.amap.com/lbs/static/main1119.css/ script srchttps://webapi.amap.com/maps?v2.0keyYOUR_KEY/script script var map new AMap.Map(mapContainer, { zoom: 16, center: [parkLng, parkLat] }); var marker new AMap.Marker({ position: [parkLng, parkLat], title: 公园中心 }); map.add(marker); /script设施标记点可以在后端把设施列表渲染成数组再循环添加Marker。AMap.Marker是基础如果要展示不同分类可以用不同的图标或颜色来区分。定位功能上我推荐前端通过 AMap.Geolocation 插件来获取坐标。它比浏览器原生Geolocation更精准还能自动把GPS坐标转换成高德坐标。AMap.plugin(AMap.Geolocation, function () { var geolocation new AMap.Geolocation({ enableHighAccuracy: true, timeout: 10000 }); geolocation.getCurrentPosition(function (status, result) { if (status complete) { // result.position 就是定位坐标 } }); });这里有个很容易踩的坑经纬度坐标系不统一。浏览器原生API返回的是WGS84坐标高德地图默认用GCJ-02坐标如果不转换你的标记点会偏移三五百米。解决方法是要么用高德插件定位要么在后端做坐标转换。4. 部署调试与常见问题排查4.1 本地开发环境搭建踩坑先说环境。我用的是Python 3.10 Django 4.2虚拟环境用venv创建。python -m venv venv source venv/bin/activate # Windows下执行 venv\Scripts\activate pip install django djangorestframework django-admin startproject park_system python manage.py startapp park第一次跑起来后记得先创建数据库迁移并创建超级用户python manage.py makemigrations park python manage.py migrate python manage.py createsuperuser这一年我见过太多同学卡在这里makemigrations后没执行migrate直接跑python manage.py runserver然后报relation park_facility does not exist。原因就是数据表还没建。如果你用的是MySQL还需要安装mysqlclient并在settings.py里配置数据库连接。Django默认的SQLite适合开发但部署到服务器上最好换成MySQL或PostgreSQL。4.2 常见问题速查表我整理了平时大家问得最多的问题直接列个表现象可能原因解决办法Admin后台样式丢失settings中DEBUGFalse时未处理静态文件使用whitenoise或python manage.py collectstatic定位上报接口返回403CSRF token未携带在fetch里带上X-CSRFToken头地图标记点和实际位置偏移几百米坐标系不统一WGS84与GCJ-02混用改为统一使用高德定位插件数据库迁移报字段类型错误DecimalField和Float混用统一使用DecimalField巡护轨迹画出来是一堆乱线记录时间乱序查询时按report_time升序排列接口返回中文乱码未设置DEFAULT_CHARSET在settings中设置DEFAULT_CHARSETutf-8这些坑都不是高深的问题但每个都能让人排查好一阵子。我的习惯是在开发初期就把这几个点规范好不要等代码写完了再来修。4.3 代码组织与后续扩展建议项目能否轻松扩展取决于一开始的代码组织。我的目录结构大致是这样park_system/ ├── manage.py ├── park_system/ # 项目配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── park/ # 业务应用 ├── models.py ├── views.py ├── urls.py ├── admin.py └── migrations/业务逻辑不要全堆在views里至少要把“距离计算”这类工具函数独立出来。我给你一个最小的分层建议models.py 只做数据模型定义views.py 里做请求处理和参数校验utils.py 放工具函数比如坐标转换、距离计算services.py 放比较重的业务逻辑比如批量查询与统计如果你后续想加“节假日人流量预测”“热门设施推荐”这样的结构会让你少重构很多代码。所谓“一条龙定制”不只是一次性交代码更重要的是交给别人一套容易读、容易改的工程。再说一个扩展方向。目前位置记录表足够支持巡护轨迹回放你只需要按时间筛选再返回给前端然后在地图上画Polyline就行了。这个功能很多毕设展示环节会加性价比很高。最后说点个人实操体会这套公园定位系统最大的价值不是技术有多难而是它把“地图”“定位”“权限”“后台管理”这些常用模块串在了一起。做这类项目我建议你第一步先搞清楚用户角色和核心操作第二步再把数据表设计好第三步才写页面。整个项目从零到跑通我一个人大约用了两周其中很大一部分时间花在地图SDK调试和权限控制上。如果你是在校学生千万不要把重心放到“代码要写得多么酷炫”上而是把设计文档、测试记录、功能演示都做到位。最后再分享一个小技巧公园定位系统这类题目的答辩演示一定要准备一个“模拟定位功能”。在电脑上演示时浏览器定位不一定准我会做一个手动输入经纬度的面板这样不管在哪都能稳定演示周边查询和轨迹回放效果会比现场找信号好很多。Django的生态决定了它是一个非常适合做业务系统的框架公园定位这个题目也把它的优势发挥得比较充分。希望这篇能帮你少走弯路把精力真正放在业务实现和系统设计上。
返回列表