news 2026/10/19 7:38:03

基于Django和Leaflet的地质野外记录与地图展示系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Django和Leaflet的地质野外记录与地图展示系统

简介:geolog-app 是一套基于 Python 与 Django 框架开发的地质类 Web 应用源码包,面向希望掌握 Django 项目结构、地质数据管理与可视化展示的开发者。压缩包共 22 个文件,包含 14 个 Python 脚本、2 个文本说明、1 个 HTML 模板、1 个 CSS 样式表、1 个 SQLite 数据库文件及部署配置,整体仅 14KB,结构紧凑,便于快速通读。源码按标准 Django 工程组织,涵盖设置文件、路由映射、视图逻辑、模型定义、模板渲染与后台管理配置等核心部分,并附带一个轻量级数据库,可直观看出地质数据在模型中的存储与调用方式;同时包含依赖清单与云端部署配置,方便在本地或服务器环境运行。目前已有 162 人学习下载,适合作为地质信息管理、课程设计或 Django 入门实战的参考资料,尤其适合需要短时间内看懂项目代码组织方式的初学者与开发人员。

1. geolog-app 是什么:一套基于 Python 的地质野外记录与地图展示系统

野外实习回到寝室整理资料,最怕的不是当天走了多少山路,而是点号对不上照片、岩性描述还留在纸质记录本里需要重新录入。这个项目的出发点就是把这条链路压缩掉:geolog-app 用 Python 写成,后端基于 Django 和 Django REST Framework,数据交换格式统一用 GeoJSON,前端地图用 Leaflet 渲染。野外用手机浏览器新增点位,填写岩性、层号、样本编号,记录会连同经纬度一起入库;回到电脑前打开同一个页面,所有点位自动落在互动地图上,点击标记就能回看当时的野外描述。

它适合地质院校的课程实习,也适合工程勘察小团队做轻量化的野外数据采集。不需要安装桌面端 GIS 软件,有一个浏览器、一台能定位的移动设备就能跑起来。

2. 技术选型与项目结构:Django 做后端,GeoJSON 做数据桥梁

2.1 技术栈:为什么是这个组合

用 Python 做这类信息管理系统,Django 是最常见的选项。它自带 Admin 后台、ORM、迁移机制,写野外记录这类 CRUD 业务几乎不用额外搭架子。接口层用 Django REST Framework 暴露 JSON,前端不管是纯网页还是以后要接小程序、桌面端,接口都能直接复用。

地图组件我一般会用 Leaflet 而不是 OpenLayers。对几个打点记录的小项目来说,Leaflet 体积小、上手快、文档也直白,OpenLayers 的那套重型工具链在这个体量下完全是杀鸡用牛刀。底图默认用 OpenStreetMap 瓦片,以后想换 ArcGIS 或者内网瓦片服务,Leaflet 只需要改一行tileLayer的 URL。

数据库本地开发用 SQLite,部署到服务器后换 PostgreSQL。之所以留这个切换余地,是因为野外记录多了以后会涉及空间查询,PostgreSQL 装 PostGIS 扩展以后可以直接跑「找半径 500 米内所有点位」这类 SQL,SQLite 做不到。项目里没有强制依赖 GIS 扩展库,GeoJSON 只作为数据导出格式,所以下载后在普通电脑上就能直接跑,不需要装一堆地理相关的 Python 包。这一点对新手很友好,跑起来不会因为缺 GDAL 之类的东西直接翻车。

2.2 目录结构:一个适合小团队的项目布局

项目的目录规划比较克制,没有上多模块复杂架构,按「配置、业务、前端」三块切分,源码解压之后大致长这样。

geolog-app/ ├── config/ # Django 项目配置 │ ├── settings.py │ └── urls.py ├── geolog/ # 核心业务模块 │ ├── models.py # 点位、分层、样本三张表 │ ├── serializers.py │ ├── views.py # CRUD 接口 + GeoJSON 导出 │ ├── geo.py # 坐标转换工具 │ ├── migrations/ │ └── urls.py ├── frontend/ │ ├── index.html # 单页入口 │ ├── map.js # Leaflet 地图渲染 │ └── style.css ├── fixtures/ │ └── initial_data.json # 演示用点位数据 ├── requirements.txt └── manage.py

config文件夹只做全局配置和根路由,geolog文件夹承载全部业务逻辑。有个细节值得注意:frontend没有单独拉一个前端工程,而是直接作为 Django 的静态文件目录使用,视图层渲染index.html即可。对小团队来说少一套 Node 构建流程,省掉 npm install 和 webpack 配置,维护成本低很多。项目跑不起来的时候排查范围也更小,问题基本都集中在 Python 这一侧。

2.3 核心数据模型:点位、分层、样本三张表

野外记录可以抽象成三个业务对象:点位是空间位置,分层是点位下的地层/岩性描述,样本是采集的实物样品。它们天然形成一对多的关系:一个点位下面有多层地层描述,每层下面又关联若干样本。模型代码是这么设计的。

# geolog/models.py from django.db import models class Station(models.Model): """野外调查点 / 打点位置""" name = models.CharField('点位名称', max_length=100) longitude = models.FloatField('经度') latitude = models.FloatField('纬度') elevation = models.FloatField('高程', null=True, blank=True) description = models.TextField('野外描述', blank=True) created_at = models.DateTimeField('记录时间', auto_now_add=True) class Meta: ordering = ['-created_at'] def __str__(self): return self.name class Strata(models.Model): """地层 / 露头分层记录""" station = models.ForeignKey( Station, on_delete=models.CASCADE, related_name='strata', verbose_name='所属点位', ) layer_no = models.IntegerField('层号') rock_type = models.CharField('岩性', max_length=50) thickness = models.FloatField('厚度(m)', default=0) description = models.TextField('岩性描述', blank=True) class Meta: ordering = ['layer_no'] class Sample(models.Model): """采集样本""" station = models.ForeignKey( Station, on_delete=models.CASCADE, related_name='samples', verbose_name='所属点位', ) sample_code = models.CharField('样本编号', max_length=50) lithology = models.CharField('岩性', max_length=50) remark = models.TextField('备注', blank=True)

经纬度字段选FloatField而不是DecimalField,这是有意的取舍。DecimalField精度更高、没有浮点数误差,但从接口接收 GPS 数据时前端传来的本来就是 JavaScript 浮点数,序列化阶段还要做字符串转换,徒增复杂度。对野外记录这个精度足够,保留到小数点后六位大概对应 0.1 米,远超手持 GPS 的定位精度。

外键上的related_name参数值得解释一下:写related_name='strata'之后,从点位反查分层数据就变成station.strata.all(),语义非常顺。如果不写这个参数,Django 默认生成station.strata_set.all(),模型多了以后可读性会差很多。on_delete=models.CASCADE表示点位删除时分层、样本记录一并删除,野外数据是强依赖关系,删了打点位置,下面的描述留着也没有意义。

2.4 序列化器:模型怎么暴露给前端

Django 模型不能直接被 JSON 序列化,DRF 的ModelSerializer负责把模型转成可传输的 JSON,同时承担接口数据的校验。

# geolog/serializers.py from rest_framework import serializers from .models import Station, Strata, Sample class StrataSerializer(serializers.ModelSerializer): class Meta: model = Strata fields = ['id', 'layer_no', 'rock_type', 'thickness', 'description'] class SampleSerializer(serializers.ModelSerializer): class Meta: model = Sample fields = ['id', 'sample_code', 'lithology', 'remark'] class StationSerializer(serializers.ModelSerializer): strata = StrataSerializer(many=True, read_only=True) samples = SampleSerializer(many=True, read_only=True) class Meta: model = Station fields = ['id', 'name', 'longitude', 'latitude', 'elevation', 'description', 'created_at', 'strata', 'samples']

嵌套序列化器的写法是让接口一次返回完整层级的关键。前端拿到一个点位的数据,strata列表和samples列表直接附带在该点位下,不需要再发第二次请求。这里read_only=True表示嵌套数据只读,点位创建时不通过这个接口批量写入分层记录,分层和样本由各自独立的接口去增删改,职责更清晰。

创建一个新点位时走StationSerializer,写入字段是name、longitude、latitude、elevation、description;外层字段里rocks_type等并不出现,前端提交时按这个字段名传 JSON 即可。理解序列化器的字段列表,就相当于理解了接口的入参和出参协议。

3. 核心模块拆解:一条野外记录从保存到显示在地图上的完整链路

3.1 后端接口:用 ModelViewSet 管理点位数据

Django REST Framework 里最省事的做法是用ModelViewSet,它把list / create / retrieve / update / partial_update / destroy六个动作一次性生成,路由注册之后就直接冒出一套完整的 REST 接口。

# geolog/views.py from rest_framework import viewsets from .models import Station from .serializers import StationSerializer from .geo import to_wgs84 class StationViewSet(viewsets.ModelViewSet): queryset = Station.objects.all() serializer_class = StationSerializer def perform_create(self, serializer): lon = serializer.validated_data['longitude'] lat = serializer.validated_data['latitude'] lon, lat = to_wgs84(lon, lat) serializer.save(longitude=lon, latitude=lat)

perform_create是写入前的最后一次拦截点。重写它的目的,是给坐标做一道兜底转换:手机浏览器定位返回的坐标大多是 GCJ-02(火星坐标系),而 Leaflet 底图用的 WGS-84,两者直接落图会有几百米偏差。常见的做法是把转换函数放到geolog/geo.py,接口层统一调to_wgs84处理,保证入库的经纬度永远统一。

路由注册也很简单,在geolog/urls.py里加一行router.register('stations', StationViewSet),前端就能通过/api/stations/访问点位资源。GET 请求拿列表,POST 请求创建点位,PUT / PATCH 更新,DELETE 删除,DRF 全包了。对一个以数据录入为主的项目,这套接口模式能覆盖绝大部分需求。

3.2 GeoJSON 导出:把数据库记录变成标准空间数据

数据库里存的是经纬度字段,但 GIS 软件不认这种自定义格式。GeoJSON 是地理空间数据的通用交换格式,QGIS、ArcGIS、PostGIS 都能直接识别,所以项目里加了一个专门生成 GeoJSON 的接口。

# geolog/views.py from django.http import JsonResponse from django.views.decorators.http import require_GET @require_GET def geojson_export(request): features = [] for station in Station.objects.all(): features.append({ "type": "Feature", "geometry": { "type": "Point", "coordinates": [station.longitude, station.latitude], }, "properties": { "name": station.name, "elevation": station.elevation, "description": station.description, }, }) return JsonResponse({ "type": "FeatureCollection", "features": features, })

这个视图的核心是构造 FeatureCollection 结构。每个点位是一条 Feature,geometry里装空间位置,properties里装属性字段。coordinates 的顺序固定为经度在前、纬度在后,这是 GeoJSON 规范的要求,写反了地图上会直接跑到海里去。

常见的误用是把lat, lon按习惯塞进去,结果点位在渲染时出现奇怪的偏移。我一般在写这个接口时会顺手把坐标顺序写进注释,避免过几个月自己回来看也踩同样的坑。QGIS 里加载这个接口地址,所有点会按属性字段自动显示成标注图层。这个接口是整个项目和外部 GIS 生态打通的关键。

3.3 前端渲染:Leaflet 消费 GeoJSON

前端frontend/map.js负责把接口返回的 GeoJSON 渲染成地图上的点。

// frontend/map.js const map = L.map('map').setView([29.30, 94.20], 7); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, }).addTo(map); fetch('/api/stations/geojson/') .then(resp => resp.json()) .then(data => { L.geoJSON(data, { pointToLayer: (feature, latlng) => L.circleMarker(latlng, { radius: 6 }), onEachFeature: (feature, layer) => { const p = feature.properties; layer.bindPopup(`<b>${p.name}</b><br>${p.description || ''}`); } }).addTo(map); });

setView的第一个参数是中心点坐标,这里默认放在项目演示区域附近,实际跑起来可以根据自己的数据改。fetch 请求的是上一节写的/api/stations/geojson/接口,L.geoJSON直接识别 FeatureCollection 结构,把每个 Point 渲染成circleMarker。

onEachFeature是交互逻辑的挂载点,这里给每个点绑定了 popup 弹窗,点击标记时显示点位名称和野外描述。整个链路其实只有几步:Django 从数据库把记录读出来,序列化成 GeoJSON,JS 拿到 GeoJSON 交给 Leaflet 渲染。理解这条链路,后面加什么功能都知道往哪个环节塞代码。

4. 本地运行指南:从拉取源码到浏览器出现点位

4.1 环境准备:虚拟环境与依赖安装

项目依赖写在requirements.txt里,核心是 Django 和 djangorestframework。拿到源码后先建虚拟环境,再装依赖,这一步别急着省。

git clone <仓库地址> geolog-app cd geolog-app python3 -m venv .venv source .venv/bin/activate # Windows 环境换成 .venv\Scripts\activate pip install -r requirements.txt

venv 这一步单独拎出来说,是因为我见过太多直接把依赖装进系统 Python 导致互相污染的情况。系统 Python 环境里装过旧版 Django,新项目一启动就报版本兼容错误,这种玄学问题本质上都是环境隔离没做。依赖安装完成后,可以用python manage.py version确认一下当前 Django 版本,避免后面接口行为对不上。

4.2 数据库初始化:迁移与示例数据

迁移是把模型定义转换成数据库表结构的过程。项目下载到本地时,如果migrations目录里已经有迁移文件,直接执行 migrate 即可;没有的话要先执行makemigrations让 Django 生成迁移文件。

python manage.py makemigrations geolog python manage.py migrate python manage.py loaddata fixtures/initial_data.json python manage.py createsuperuser

loaddata这一步不是必需流程,但强烈建议做。initial_data.json里预置了几个演示点位,导入后地图上立刻有数据可看,不用自己一笔一笔录。createsuperuser是创建后台管理员账号,Django Admin 在这个项目里承担数据管理入口,访问/admin/登录后可以查看和编辑点位记录,相当于一个现成的后台面板。

4.3 启动与验证:手动测一遍完整流程

接着启动开发服务器。

python manage.py runserver 8000

浏览器打开http://127.0.0.1:8000/应该是地图页面,能看到实验点铺在地图上。再用命令行手动创建一条记录,验证接口链路是否通。

curl -X POST http://127.0.0.1:8000/api/stations/ \ -H "Content-Type: application/json" \ -d '{"name":"测试点A","longitude":88.10,"latitude":29.20}'

curl 实际执行时如果 JSON 里的双引号被 shell 吃掉,可以在 Windows PowerShell 里改用单引号包裹 JSON。正常响应会返回 201 和新增记录的完整 JSON,包含自动生成的id和created_at。刷新前端地图页面,新点应该出现在对应坐标位置。

到这里项目就完整跑起来了:数据库有表、接口有响应、地图能渲染、后台能管理。想验证 GeoJSON 的兼容性,把http://127.0.0.1:8000/api/stations/geojson/这个地址贴到 QGIS 的图层管理器里,选择数据类型为 GeoJSON,点就能直接加载进 GIS 软件。

5. 避坑指南:地图白屏、坐标偏移和序列化时区问题

5.1 地图白屏:Leaflet 容器没有高度

现象:页面打开后右上角和左下角能看到 Leaflet 的缩放按钮,但地图区域整块空白。

原因:<div id="map">没有设置 CSS 高度。Leaflet 初始化时读取容器的高度,如果高度为 0,地图切片就渲染不出来,但控件照常显示。

解决:在frontend/style.css里给地图容器指定高度。

#map { height: 500px; width: 100%; }

这是 Leaflet 项目最常见的白屏原因,遇到地图区域显示不出来,先检查容器高度,八成是这个问题。

5.2 坐标偏移:手机定位和底图坐标系不一致

现象:手机浏览器上定位记录的点位,回到电脑地图上显示时位置偏移了几百米,而且偏移方向在不同区域还不一样。

原因:手机端 Web 定位接口返回的一般是 GCJ-02 坐标,而 OpenStreetMap 底图用的是 WGS-84。两者在大多数地区有几百米偏差,直接叠加自然会对不上。

解决:写一个坐标转换函数放进geolog/geo.py,在perform_create里对所有新增点位做一次转换。代码已经在第三章展示,核心是在serializer.save()之前调用to_wgs84覆盖经纬度值。转换算法的具体公式网上有现成实现,直接拿来找个测试点验证一下误差在可接受范围即可。

5.3 时间字段多出 +08:00 后缀

现象:接口返回的created_at值是2025-06-01T10:30:00+08:00,前端new Date()解析后显示正常,但直接字符串拼接展示就变成带时区后缀,和手填的日期格式对不上。

原因:DRF 默认把 DateTimeField 序列化成 ISO 8601 格式,带时区偏移。本地 SQLite 存的是2025-06-01 10:30:00,序列化后自动补上了东八区后缀。

解决:在StationSerializer里覆盖字段格式。

created_at = serializers.DateTimeField(format='%Y-%m-%d %H:%M')

加这一行之后接口返回的时间就是2025-06-01 10:30,前端不用再做任何处理。需要注意,这个格式会影响所有用到该字段的接口,如果同时存在需要 ISO 格式的场景,可以再单独加一个只返回datetime类型的序列化器。

5.4 旧 fixtures 导入时报字段不存在

现象:python manage.py loaddata fixtures/initial_data.json执行时直接报错,提示某个字段在模型里找不到。

原因:fixtures 文件是用旧版本模型导出后留在项目里的,后来模型加了字段或者改了字段名,JSON 里的字段列表和当前模型对不上。

解决:把报错提到的字段在Station模型里找一下,确认是新版本里被删掉或者改名了,手动编辑initial_data.json删掉对应键值,或者直接重新生成一份 fixtures。

python manage.py dumpdata geolog > fixtures/initial_data.json

dumpdata导出的是当前模型结构下的合法数据,重新生成的 fixtures 一定能正常导入。这个小坑提醒我们,fixtures 文件不是永久有效的,模型一变它就要跟着动。

6. 二次开发思路:给项目加产状信息和 CSV 导出

拿到项目之后最想干的事通常是加野外产状数据。地质记录里走向、倾向、倾角是必填项,现在Strata模型没有这三个字段,改造步骤很简单:在模型定义中追加三个浮点数字段,生成并执行迁移。

# 在 Strata 模型里追加 strike = models.FloatField('走向', null=True, blank=True) dip_direction = models.FloatField('倾向', null=True, blank=True) dip_angle = models.FloatField('倾角', null=True, blank=True)

改完执行python manage.py makemigrations geolog && python manage.py migrate,再把字段名加进StrataSerializer.fields列表,前端strata嵌套数据里就会自动带上产状信息。

加一个 CSV 导出接口是另一种实用改造。单位领导不会打开 GeoJSON,但 CSV 用 WPS 就能看。我一般在项目里这样加:

import csv from django.http import HttpResponse def export_csv(request): resp = HttpResponse(content_type='text/csv') resp['Content-Disposition'] = 'attachment; filename="stations.csv"' writer = csv.writer(resp) writer.writerow(['name', 'longitude', 'latitude', 'elevation', 'description']) for s in Station.objects.all(): writer.writerow([s.name, s.longitude, s.latitude, s.elevation, s.description]) return resp

Content-Disposition头的attachment会让浏览器直接把响应下载成文件。这条改造验证起来也快,访问新接口能拿到 CSV、字段列头和数据库记录一致即可。

从做这个项目以后我养成了一个习惯,每次加字段都强制走一遍「模型 → 迁移 → 序列化器 → 前端展示」这条全链路,而不是改完模型就当完成。跟着这条线检查一遍,字段丢失、接口报错这类问题基本都能在提交之前拦下来。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 7:35:20

2026年项目经理评职称必看:软考/建造师等证书对应层级一次说清

前不久在后台看到一条留言&#xff1a;非全日制本科、八年项目经验&#xff0c;手里只有PMP证书&#xff0c;想报中级职称&#xff0c;材料递上去被退回来了&#xff0c;原因是“证书不在目录内”。这条留言我印象很深&#xff0c;因为它代表了一大批项目经理的真实困境。很多做…

作者头像 李华
网站建设 2026/10/19 7:34:58

Linux网络配置:nmcli命令与ifcfg-*文件关联及避坑指南

搞 Linux 运维这些年&#xff0c;网络配置大概是新手最容易翻车、老手也偶尔头疼的一块。最常见的两个操作方式&#xff0c;一个是敲 nmcli 命令让 NetworkManager 干活&#xff0c;另一个是直接编辑 /etc/sysconfig/network-scripts/ 下面的 ifcfg-* 网卡配置文件。两条路都能…

作者头像 李华
网站建设 2026/10/19 7:34:00

Python深度学习入门实战:从环境搭建到图像分类全流程

“Python深度学习”这几个字放在一起的时候&#xff0c;不少人的第一反应是&#xff1a;又要学Python、又要补数学、还要看懂模型结构&#xff0c;门槛是不是太高了&#xff1f;我在这个方向折腾了几年&#xff0c;从照着教程跑MNIST都跑不明白&#xff0c;到后来独立负责一套图…

作者头像 李华
网站建设 2026/10/19 7:33:24

医疗影像分割中SAM如何把病灶边界做得更精细

去年有个项目要做肺结节风险分级前的病灶分割&#xff0c;某医院给了一批边界极不规则的亚实性结节影像。UNet系列、DeepLab系列我前后试过一圈&#xff0c;医生反馈永远是“肿瘤是圈出来了&#xff0c;但边界就像用蜡笔画的&#xff0c;很多细小的分叶和毛刺都抹平了”。后来我…

作者头像 李华
网站建设 2026/10/19 7:32:42

VirtualWife:本地化Live2D+LLM虚拟人直播框架

简介&#xff1a;VirtualWife是一个开源的虚拟数字人项目&#xff0c;面向AI开发者、直播技术实践者及AIGC应用探索者&#xff0c;旨在降低B站虚拟直播的技术门槛&#xff0c;提供可快速部署、可定制的本地化数字人交互方案。资源包共246个文件&#xff0c;涵盖97个Python核心逻…

作者头像 李华
网站建设 2026/10/19 7:32:39

AI辅助编程翻车自救指南:双模型分工与任务书驱动实战

1. 一次翻车让我重新理解AI辅助编程前几天帮一个学弟看他的量化作业&#xff0c;差点看吐了。代码能跑&#xff0c;结果却不对&#xff0c;回测净值曲线画出来像心电图&#xff0c;指标算得有模有样但一问细节就露馅——这不是学弟一个人的问题&#xff0c;这是他让AI全流程代写…

作者头像 李华