跳转至

Django制作地图

摘要

基于Django、Pillow和Leaflet制作的地图展示,可以理解为参考文章的”引进版“。由于大部分功能在参考文章中已经实现,故在此不多赘述,只介绍一些新的部分和改动,以及一些注意事项。

参考文章

Maps with Django⁽¹⁾: GeoDjango, SpatiaLite & Leaflet
Maps with Django⁽²⁾: GeoDjango, PostGIS & Leaflet
Maps with Django⁽³⁾: GeoDjango, Pillow & GPS

依赖

项目使用uv进行依赖管理,安装的依赖如下:

  • django
  • django-leaflet>=0.33.0
  • pillow>=12.3.0
  • django-unfold>=0.100.0
  • GDAL

django安装最新版即可,其中GDAL需要单独安装,安装自己电脑系统和版本进行安装

GDAL

在安装django和gdal之后,根据django官方文档需要在settings.py中进行设置

VENV_SITE_PACKAGES = BASE_DIR / ".venv" / "Lib" / "site-packages"

proj_ltb = VENV_SITE_PACKAGES / "osgeo" / "data" / "proj"
os.environ["PROJ_LIB"] = proj_ltb.as_posix()

GDAL_LIBRARY_PATH = VENV_SITE_PACKAGES / "osgeo" / "gdal.dll"
GEOS_LIBRARY_PATH = VENV_SITE_PACKAGES / "osgeo" / "geos_c.dll"

一些改动

使用了django-leaflet替代了leaflet.js,所以一些地方需要进行修改

settings.py

LEAFLET_CONFIG = {
    "DEFAULT_CENTER": (23.1291, 113.2644),
    "DEFAULT_ZOOM": 12,
    "MIN_ZOOM": 3,
    "MAX_ZOOM": 18,
    "TILES": [
        (
            "高德街道",
            "https://wprd0{s}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
            {
                "subdomains": "1234",
                "attribution": "© 高德地图",
            },
        ),
        # 高德卫星影像
        (
            "高德卫星",
            "https://wprd0{s}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=6",
            {
                "subdomains": "1234",
                "attribution": "© 高德地图",
            },
        ),
    ],
    "DEFAULT_TILES": "高德街道",
}

需要注意的是,参考文章中使用的OpenStreetMap瓦片源无法显示,这里替换成了国内的高德瓦片源。

map.html

{% load leaflet_tags %} {% load static %}
<!doctype html>
<html lang="en">
  <head>
    <title>Markers Map</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    {% leaflet_css %} {% leaflet_js %}
    <style>
      body {
        margin: 0;
        padding: 0;
      }
      html,
      body,
      #map {
        height: 100%;
        width: 100vw;
      }
      figure {
        margin: 0;
        padding: 0;
        text-align: center;
      }
      img {
        max-width: 100%;
        height: auto;
      }
    </style>
  </head>
  <body>
    {{ markers|json_script:"markers" }}
    <script src="{% static 'map.js' %}"></script>
    {% leaflet_map "map" callback="window.map_init" %}
  </body>
</html>

map.js

相对应的js也需进行修改

window.map_init = function (map, options) {
  const data = JSON.parse(document.getElementById("markers").textContent);
  const feature = L.geoJSON(data)
    .bindPopup(
      function (layer) {
        const name = layer.feature.properties.name;
        const img = layer.feature.properties.image;
        if (!img) return name;
        return (
          "<figure>" +
          '<img src="/media/' +
          img.split("/").map(encodeURIComponent).join("/") +
          '">' +
          "<figcaption>" +
          name +
          "</figcaption>" +
          "</figure>"
        );
      },
      // 设置图片的最小宽度
      { minWidth: 200 },
    )
    .addTo(map);

  const bounds = feature.getBounds();
  if (bounds.isValid()) {
    map.fitBounds(bounds);
  }
};

新增

添加了unfold作为后台admin,具备现代化和美观

settings.py

将unfold添加到INSTALLED_APPS最上面

INSTALLED_APPS = [
    "unfold",
    "django_cotton",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "django.contrib.gis",
    "leaflet",
    "markers",
]

admin.py

from django.contrib import admin
from leaflet.admin import LeafletGeoAdmin
from unfold.admin import ModelAdmin

from markers.models import Marker


@admin.register(Marker)
class MarkerAdmin(LeafletGeoAdmin, ModelAdmin):
    list_display = ("name", "image")
    readonly_fields = ("location",)
    search_fields = ("name",)

这里使用了LeafletGeoAdmin,而不是使用GISModelAdmin,也避免了与unfold的冲突。

apps.py

该文件需要在markers目录下新建,主要为了后台中文展示

from django.apps import AppConfig


class MarkersConfig(AppConfig):
    name = "markers"
    verbose_name = "地图标记"

models.py

添加了中文,方便后台使用

from django.contrib.gis.db import models
from django.core.validators import FileExtensionValidator
from django.db.models import Manager

from markers.utils import get_point


class Marker(models.Model):
    objects: Manager

    name = models.CharField(max_length=200, verbose_name="名称")
    location = models.PointField(verbose_name="坐标")
    image = models.ImageField(
        verbose_name="图片",
        null=True,
        upload_to="images/markers/",
        validators=[
            FileExtensionValidator(allowed_extensions=["jpg", "jpeg"]),
        ],
    )

    class Meta:
        verbose_name = "标记"
        verbose_name_plural = "标记"

    def __str__(self):
        return str(self.name)

    def save(self, *args, **kwargs):
        if self.image:
            point = get_point(self.image)
            if point:
                self.location = point
        super().save(*args, **kwargs)

界面效果

登录界面 unfold CURD 其实该项目比较简单,使用django自带的admin足以,因为之前没用过这个后台,为了后续项目的开发,提前用这个项目对unfold做了测试,感觉还不错。

地图展示

map

结论

由于从事相关GIS行业,想着自己实现一次地图展示的功能,凭借对django的理解,根据国外文章进行的二次实现,实现了使用django进行地图的展示。其中用django-leaflet替换leaflet以及用unfold进行后台的美化,只是在原项目上一些微不足道的改动,也是对心中想法的一次尝试。


参考资料

评论