#########################################################################
#
# Copyright (C) 2017 OSGeo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################
import json
import uuid
import logging
import requests
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.gis.geos import Polygon
from django.template.defaultfilters import slugify
from geonode import GeoNodeException
from geonode.layers.models import Dataset
from geonode.layers.utils import get_valid_name
from geonode.resource.manager import resource_manager
from geonode.geoserver.helpers import gs_catalog, ogc_server_settings, create_geoserver_db_featurestore
[docs]
logger = logging.getLogger(__name__)
[docs]
BBOX = [-180, -90, 180, 90]
[docs]
DATA_QUALITY_MESSAGE = "Created with GeoNode"
[docs]
def create_dataset(name, title, owner_name, geometry_type, attributes=None):
"""
Create an empty layer in GeoServer and register it in GeoNode.
"""
# first validate parameters
if geometry_type not in ("Point", "LineString", "Polygon"):
msg = "geometry must be Point, LineString or Polygon"
logger.error(msg)
raise GeoNodeException(msg)
name = get_valid_name(name)
# we can proceed
logger.debug("Creating the layer in GeoServer")
workspace, datastore = create_gs_dataset(name, title, geometry_type, attributes)
logger.debug("Creating the layer in GeoNode")
return create_gn_dataset(workspace, datastore, name, title, owner_name)
[docs]
def create_gn_dataset(workspace, datastore, name, title, owner_name):
"""
Associate a layer in GeoNode for a given layer in GeoServer.
"""
owner = get_user_model().objects.get(username=owner_name)
layer = resource_manager.create(
str(uuid.uuid4()),
resource_type=Dataset,
defaults=dict(
name=name,
workspace=workspace.name,
store=datastore.name,
subtype="vector",
alternate=f"{workspace.name}:{name}",
title=title,
owner=owner,
srid="EPSG:4326",
bbox_polygon=Polygon.from_bbox(BBOX),
ll_bbox_polygon=Polygon.from_bbox(BBOX),
data_quality_statement=DATA_QUALITY_MESSAGE,
),
)
to_update = {}
if settings.ADMIN_MODERATE_UPLOADS:
to_update["is_approved"] = to_update["was_approved"] = False
if settings.RESOURCE_PUBLISHING:
to_update["is_published"] = to_update["was_published"] = False
resource_manager.update(layer.uuid, instance=layer, vals=to_update)
resource_manager.set_thumbnail(None, instance=layer)
return layer
[docs]
def get_attributes(geometry_type, json_attrs=None):
"""
Convert a JSON representation of attributes to a Python representation.
Parameters:
- json_attrs:
.. code-block:: json
{
"field_str": "string",
"field_int": "integer",
"field_date": "date",
"field_float": "float"
}
- geometry_type: A string which can be "Point", "LineString", or "Polygon"
Output:
.. code-block:: python
[
['the_geom', u'com.vividsolutions.jts.geom.Polygon', {'nillable': False}],
['field_str', 'java.lang.String', {'nillable': True}],
['field_int', 'java.lang.Integer', {'nillable': True}],
['field_date', 'java.util.Date', {'nillable': True}],
['field_float', 'java.lang.Float', {'nillable': True}]
]
"""
lattrs = []
gattr = []
gattr.append("the_geom")
gattr.append(f"com.vividsolutions.jts.geom.{geometry_type}")
gattr.append({"nillable": False})
lattrs.append(gattr)
if json_attrs:
jattrs = json.loads(json_attrs)
for jattr in jattrs.items():
lattr = []
attr_name = slugify(jattr[0])
attr_type = jattr[1].lower()
if len(attr_name) == 0:
msg = f"You must provide an attribute name for attribute of type {attr_type}"
logger.error(msg)
raise GeoNodeException(msg)
if attr_type not in ("float", "date", "string", "integer"):
msg = f"{attr_type} is not a valid type for attribute {attr_name}"
logger.error(msg)
raise GeoNodeException(msg)
if attr_type == "date":
attr_type = f"java.util.{(attr_type[:1].upper() + attr_type[1:])}"
else:
attr_type = f"java.lang.{(attr_type[:1].upper() + attr_type[1:])}"
lattr.append(attr_name)
lattr.append(attr_type)
lattr.append({"nillable": True})
lattrs.append(lattr)
return lattrs
[docs]
def get_or_create_datastore(cat, workspace=None, charset="UTF-8"):
"""
Get a PostGIS database store or create it in GeoServer if does not exist.
"""
dsname = ogc_server_settings.datastore_db["NAME"]
ds = create_geoserver_db_featurestore(store_name=dsname, workspace=workspace)
return ds
[docs]
def create_gs_dataset(name, title, geometry_type, attributes=None):
"""
Create an empty PostGIS layer in GeoServer with a given name, title,
geometry_type and attributes.
"""
native_name = name
cat = gs_catalog
# get workspace and store
workspace = cat.get_default_workspace()
# get (or create the datastore)
datastore = get_or_create_datastore(cat, workspace)
# check if datastore is of PostGIS type
if datastore.type != "PostGIS":
msg = "To use the createlayer application you must use PostGIS"
logger.error(msg)
raise GeoNodeException(msg)
# check if layer is existing
resources = datastore.get_resources()
for resource in resources:
if resource.name == name:
msg = f"There is already a layer named {name} in {workspace}"
logger.error(msg)
raise GeoNodeException(msg)
attributes = get_attributes(geometry_type, attributes)
attributes_block = "<attributes>"
for spec in attributes:
att_name, binding, opts = spec
nillable = opts.get("nillable", False)
attributes_block += (
"<attribute>"
f"<name>{att_name}</name>"
f"<binding>{binding}</binding>"
f"<nillable>{nillable}</nillable>"
"</attribute>"
)
attributes_block += "</attributes>"
# TODO implement others srs and not only EPSG:4326
xml = (
"<featureType>"
f"<name>{name}</name>"
f"<nativeName>{native_name}</nativeName>"
f"<title>{title}</title>"
"<srs>EPSG:4326</srs>"
f"<latLonBoundingBox><minx>{BBOX[0]}</minx><maxx>{BBOX[2]}</maxx><miny>{BBOX[1]}</miny><maxy>{BBOX[3]}</maxy>"
f"<crs>EPSG:4326</crs></latLonBoundingBox>"
f"{attributes_block}"
"</featureType>"
)
url = f"{ogc_server_settings.rest}/workspaces/{workspace.name}/datastores/{datastore.name}/featuretypes"
headers = {"Content-Type": "application/xml"}
_user, _password = ogc_server_settings.credentials
req = requests.post(url, data=xml, headers=headers, auth=(_user, _password))
if req.status_code != 201:
logger.error(f"Request status code was: {req.status_code}")
logger.error(f"Response was: {req.text}")
raise Exception(f"Dataset could not be created in GeoServer {req.text}")
cat.reload()
return workspace, datastore