2

I want to extract polygon geometry data from a PostGIS database using python within a view and add it to my leaflet map within a template. The easiest way seemed to be to extract the data and convert it to GeoJSON using the postgis function ST_AsGeoJSON in my Django view and then render it to the template as context within the L.geoJSON(GEOJSON).addTo(map) function.

This does not work. On requesting the map page, the map is now blank and as it seems the GeoJSON is not recognised. I have been able to pass a hard-coded polygon from a view and add it to a map but the geometry data in my postgis database simply isn't valid.

Here is a view with a hardcoded polygon that is successfully printed on the map:

from django.shortcuts import render

def map_view(request, *args, **kwargs):
    geo_json={
        "type": "FeatureCollection",
          "features": [
            {
              "type": "Feature",
              "properties": {},
              "geometry": {
                "type": "Polygon",
                "coordinates": [
                  [
                    [
                      -0.10746002197265625,
                      51.505537109466715
                    ],
                    [
                      -0.11466979980468751,
                      51.498377681772325
                    ],
                    [
                      -0.0968170166015625,
                      51.493568479510415
                    ],
                    [
                      -0.09080886840820312,
                      51.502438390761164
                    ],
                    [
                      -0.10746002197265625,
                      51.505537109466715
                    ]
                  ]
                ]
              }
            }
          ]
        }

    return render(request ,'map.html', {'geo_json': geo_json})

The map template looks as follows:

<!DOCTYPE html>
<html>
<head>
    <title>Map Page</title>
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
    <style>
        #map {  position: relative;
                width: 600px;
                height: 775px;
                border: 3px solid #000000;}
    </style>    
</head>
<body>
    <div id="map"></div>
    <script>
        var map = L.map('map').setView([54.8,-4.45],6);

        L.tileLayer('https://api.maptiler.com/maps/streets/{z}/{x}/{y}.png?key=9GKOA9jJ3jCIWFUd8k00', {attribution: '<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> <a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>',}).addTo(map);

        L.geoJSON({{ geo_json | safe }}).addTo(map);

    </script>

</body>
</html>

Here is the leaflet map with the polygon added

Now when I try to get pass GeoJSON from my postgis database using my new view it doesn't work:

import psycopg2
from django.shortcuts import render

def map_view(request, *args, **kwargs):

    connection = psycopg2.connect(database="electio5_geekdata",user="electio5_blake", password="dummypassword", host='localhost')
    cursor = connection.cursor()
    cursor.execute("select st_AsGeoJSON(shape) from boris_constituency limit 1")
    varpoly=cursor.fetchall()

    geo_json={
        "type": "FeatureCollection",
          "features": [
            {
              "type": "Feature",
              "properties": {},
              "geometry": varpoly
            }
          ]
        }

    return render(request ,'map.html', {'geo_json': geo_json})

I notice that the format of the GeoJSON is slightly different when I output it:-

IS FINE

{'type': 'FeatureCollection', 'features': [{'type': 'Feature', 'properties': {}, 'geometry': {'type': 'Polygon', 'coordinates' and so on and so on

IS A PROBLEM

{'type': 'FeatureCollection', 'features': [{'type': 'Feature', 'properties': {}, 'geometry': [('{"type":"MultiPolygon","coordinates" and so on and so on

The problematic GeoJSON has extra brackets and a quote preceding the second "type" key

So my question is:-

1/ Is it possible to reformat the problematic GeoJSON? I had difficulty stripping out the unwanted characters which wrap the list

2/ Or can I extract just the co-ordinates and pass those to the relevant part of geo_json?

3/ Or any way at all I can extract the polygon data from postgis and add it to the leaftlet map

btw you may wonder why I'm using a cursor rather than using the Django model object which has a GeoJSON method. This approach gave me a GDAL error due to the libraries not being properly configured and is a problem for another day!

Big thanks for your attention to this.

Phil #anoobintrouble

6
  • I think the problem is that fetchAll is giving you a list and you are using it as if it was just one element. So you need to use varpoly[0] or iterate varpoly and each polygon turn to a feature in the features array of the GeoJSON.
    – cabesuon
    Commented Jan 8, 2020 at 19:20
  • Thank you. True indeed so I referenced the specific element as you suggest. This cuts out the extra brackets which were added to wrap the array. But it still doesn't work. Now I have {'type': 'FeatureCollection', 'features': [{'type': 'Feature', 'properties': {}, 'geometry': '{"type":"MultiPolygon","coordinates": still with a single quote preceding the second type statement which I think is causing a problem. Where has it come from and can it be got rid of?
    – blake
    Commented Jan 8, 2020 at 19:54
  • Use json.loads to get the object, remember that it is giving you a string in geojson format.
    – cabesuon
    Commented Jan 8, 2020 at 20:44
  • Thank you very much for your help and patience. How and where should I use 'json.loads'? Does it go in the view or the template? Is it just a case of adding a simple assignment somewhere?
    – blake
    Commented Jan 8, 2020 at 23:21
  • 1
    In your code it should be "geometry": json.loads(varpoly[0]), that convert the string value in json format to a "geojson object" (python json basic usage)
    – cabesuon
    Commented Jan 9, 2020 at 2:14

2 Answers 2

5

I was given the answer by "cabesuon" as you will see in the comments above. But I'll consolidate everything to hopefully help future users. Here is a way you can extract geometry (polygon) data from a PostGIS database and then render it to a template and add it to a leaflet map using the Django web framework.

The view below extracts the data, converts it to GeoJSON and then returns it to the map.html template:-

from django.shortcuts import render
import json
import psycopg2

def map_view(request, *args, **kwargs):

    connection = psycopg2.connect(database="electio5_geekdata",user="electio5_blake", password="adummypassword", host='localhost')
    cursor = connection.cursor()
    cursor.execute("select name, st_AsGeoJSON(shape) from boris_constituency limit 1")

    varcons=cursor.fetchone()

    geo_json={
              "type": "Feature",
              "name": varcons[0],
              "properties": {},
              "geometry": json.loads(varcons[1])
            }

    return render(request ,'map.html', {'geo_json': geo_json})

The map template below picks up the geo_json context and adds it to the leaflet map, plotting the polygon geometry (which in this case is the UK parliamentary constituency of "Aldershot"):-

<!DOCTYPE html>
<html>
<head>
    <title>Map Page</title>
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
    <style>
        #map {  position: relative;
                width: 600px;
                height: 775px;
                border: 3px solid #000000;}
    </style>    
</head>
<body>
    <div id="map"></div>
    <script>
        var map = L.map('map').setView([54.8,-4.45],6);

        L.tileLayer('https://api.maptiler.com/maps/streets/{z}/{x}/{y}.png?key=9GKOA9jJ3jCIWFUd8k00', {attribution: '<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> <a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>',}).addTo(map);

        L.geoJSON({{ geo_json | safe }}).addTo(map);

    </script>

    {{ geo_json }}

</body>
</html>

here is the geometry plotted on the map

A couple of notes:-

1/ I have used pure python to access the database. I really should be using GeoDjango , calling the geometry like so:-

from boris.models import constituency
obj=constituency.objects.get(id=1)
geo_json=obj.shape.geojson

but got a GDAL_ERROR 1: b'PROJ: proj_create_from_database: Cannot find proj.db'

I believe the GDAL library isn't configured properly - I may try and fix this

2/ I now intend to make this application interactive, letting the user select constituencies, regions or finding their constituency based on their location and returning as much interesting information as I can.

Feel free to comment, correct and add anything useful.

Phil #anoobinneed

1
  • Have you found any solution? I've recently got the same error message
    – joninx
    Commented Jan 20, 2022 at 16:32
2

1.model created

from django.db import models
from django.contrib.gis.db import models

# Create your models here.

class Locate(models.Model):
    name=models.CharField(max_length=200)
    location =models.PointField(srid=4326)

2.create a geojson in views tobe dispalyed

from django.shortcuts import render
from django.views.generic import TemplateView
from django.core.serializers import serialize
from django.http import HttpResponse
from .models import Locate

# Create your views here.

class HomePageView(TemplateView):
    template_name='index.html'

# county views

def locate_datasets(request):
    locate=serialize('geojson',Locate.objects.all())
    return HttpResponse(locate,content_type='application/json')

3.create a url in your app/urls.py

from django.conf.urls import url from djgeojson.views import GeoJSONLayerView from .views import HomePageView,locate_datasets

#create your urls here


urlpatterns=[
    url(r'^$',HomePageView.as_view(),name='home'),
    url(r'^locate_data/$',locate_datasets,name='locate'),

]

4.template

using geojson ajax to load our data

<!DOCTYPE html>
<html lang="en">

{% load static %}
{% load leaflet_tags %}

<head>

    {% leaflet_js %}
    {% leaflet_css %}
</head>


<body>

<script>
 function our_layers(map,options){

 var locate = new L.GeoJSON.AJAX("{% url 'locate' %}",{}).addTo(map);

</script>

 {% leaflet_map "map" callback="window.our_layers" %}

</body>
 <script src="{% static 'leaflet.ajax.js' %}"></script>
</html>

Hope this solves your problem

1

Not the answer you're looking for? Browse other questions tagged or ask your own question.