Sharing Geospatial Data with OGC API, pygeoapi and MongoDB
Tags: docker, mongodb, tutorial, opensource
Published: 2021-12-21 09:20:11

Geospatial data can be defined as any dataset which has a location tag attached to it, _i.e._: _a pair of coordinates which allows us to position it precisely at the surface of the earth_. In this post we are going to describe how we can make geospatial data available on the web, trough a RESTfull API, so that ourselves or others can do something with it, for instance creating a map. If it is true you can create a map using a static file, using an API has numerous advantages like **scalability**, **reliability** and **updatability**. In order to do this, we will use three things: a dataset, a standard and a stack of Free and Open Source Software (FOSS).

## Dataset
In this tutorial we will share a dataset with local shops and products in the city of Barcelona, kindly provided by [sawcer](https://www.sawcer.com).

![record from sawcer dataset](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbqxkkmfgib6p5qrcdry.png)

Each record describes characteristics of the shop such as `name`, `address`, `website`, which `products` are sold (_e.g._: Gluten free donut) and, last but not the least, its location as a point geometry, with `x,y` coordinates.

## Standard
Standards are the _glue_ in the server-client architecture. The [Open Geospatial Consortium](https://www.ogc.org/)(OGC) is a member organisation, which publishes standards to ensure the maximum degree of interoperability between geospatial data and services. In practice interoperability would mean that you could write a _client application that talks to any server, without knowing any details about that server's implementation_.
In this tutorial we will use the [OGC API Features]( ogcapi.ogc.org/dev/features) standard, which was designed for sharing feature data over the web (if you are wondering what a feature is, you can check out [this](https://www.w3.org/TR/sdw-bp/#spatial-things-features-and-geometry) article). These are some of its main traits, which make it very convenient to use:
- Modular
- RESTfull
- Recommended encodings (e.g.: [GeoJSON](https://geojson.org/), HTML)
- Use of [OpenAPI](https://www.openapis.org/)

## Software Stack
In order to publish the dataset using the OGC API Features standard, we need a software which implements the standard. In this tutorial we will use [pygeoapi](https://pygeoapi.io/), which is a python server implementation, released under a FOSS ([MIT](https://github.com/geopython/pygeoapi/blob/master/LICENSE.md)) license. pygeoapi needs a backend to store the data. For that we will use the [MongoDB](https://www.mongodb.com/) document oriented database. In order to make deployment easier, the complete stack was virtualised into a set of [docker](https://www.docker.com/) containers, and orchestrated using [docker-compose](https://docs.docker.com/compose/).

![Software stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u440ki4nhg9rm2xj00x7.png)

## Putting it all together
In order to publish the dataset, you can clone [this](https://github.com/emotional-cities/pygeoapi/tree/mongo) repository. If you navigate to the docker/examples/mongo folder, you will find [this](https://github.com/emotional-cities/pygeoapi/blob/mongo/docker/examples/mongo/docker-compose.yml) docker-composition:

```yaml
version: '3.3'

services:
  pygeoapi:
    image: geopython/pygeoapi:latest

    container_name: pygeoapi_mongo

    entrypoint:
      - /mongo-entrypoint.sh

    ports:
      - 5000:80

    volumes:
      - ./pygeoapi/docker.config.yml:/pygeoapi/local.config.yml
      - ./pygeoapi/mongo-entrypoint.sh:/mongo-entrypoint.sh
      - ./pygeoapi/wait-for-mongo.sh:/wait-for-mongo.sh
    links:
      - mongo
    depends_on:
      - mongo

  mongo:
    image: mongo
    container_name: mongo
    ports:
      - 27017:27017
    volumes:
      - ./docker-entrypoint-initdb.d/add_data.sh:/docker-entrypoint-initdb.d/add_data.sh:ro
      - ./mongo_data/:/mongo_data
    environment:
        MONGO_INITDB_DATABASE: sawcer

  mongo-express:
    image: mongo-express
    restart: always
    container_name: mongo_express
    links:
      - mongo
    depends_on:
      - mongo
    ports:
      - 8081:8081
mongodb://root:example@mongo:27017/


volumes:
  mongo_data: {}

```

Here is what happens behind the scenes, when you type `docker-compose up`:

1. The `mongo` container starts, a database is initialised and the data is injected into a collection. The data is pulled from a GeoJSON [file](https://github.com/emotional-cities/pygeoapi/blob/mongo/docker/examples/mongo/mongo_data/shops.geojson) in the `mongo_data` folder. If you are trying this with your own data, it is worth to mention that mongo ingests the features array, **without the outer element**. You can transform a regular GeoJSON file into MongoDB-consumable JSON, using the [jq](https://stedolan.github.io/jq/) command-line utility: `jq --compact-output ".features" shops-orig.geojson > shops.geojson`
2. Once the database is live, the `pygeoapi` container starts. The configuration of the service is on [this](https://github.com/emotional-cities/pygeoapi/blob/mongo/docker/examples/mongo/pygeoapi/docker.config.yml) file, which is mounted by the container. In particular, [this](https://github.com/emotional-cities/pygeoapi/blob/mongo/docker/examples/mongo/pygeoapi/docker.config.yml#L496) bit is where the sawcer dataset is exposed from the MongoDB backend:

```yaml
    sawcer:
        type: collection
        title: Shops and Products 
        description: SEARCH & FIND & SHARE where to get ingredients in local shops
        keywords:
            - cases
        links:
            - type: text/html
              rel: canonical
              title: information
              href: https://www.sawcer.com/
              hreflang: en-US
        extents:
            spatial:
                bbox: [-180, -90, 180, 90]
                crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
            temporal:
                begin: 2011-11-11
                end: null  # or empty
        providers:
            - type: feature
              name: MongoDB
              data: mongodb://mongo:27017/sawcer
              collection: shops
```
At this stage, the pygeoapi is running on port `5000`: `http://localhost:5000`. You can access the sawcer dataset at: `http://localhost:5000/collections/sawcer`
You can retrieve the collection items at:
`http://localhost:5000/collections/sawcer/items`
The default response is in html, but you can change it using the `f` (format) parameter. The server supports both, JSON and JSON-LD encodings. For instance:
`http://localhost:5000/collections/sawcer/items?f=jsonld`
You can access one particular item, using its id:
`http://localhost:5000/collections/sawcer/items/61afae9fc6bf8d516533620f`

![Collection item](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewfi9ee5d39lerwjdytw.jpeg)

If you want to explore the MongoDB collection, you can use the provided mongo-express user interface, running on port `8081`: `http://localhost:8081`

## What's next
Making your geospatial dataset available on the web using a standard, unlocks a world of possibilities. It means that many existing (or future) client applications will be able to read your data, out-of-the-box. For instance, anyone can use [QGIS](https://www.qgis.org/en/site/), [Esri ArcGIS](https://www.esri.com/en-us/arcgis/about-arcgis/overview), [React-leaflet](https://react-leaflet.js.org/), [OpenLayers](https://openlayers.org/) or [Python OWSlib](https://geopython.github.io/OWSLib/) to pull your data and analyse it, or create products or services on top of it.

![OGC API Features on QGIS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xy4wsybp6g9sbba94bzb.png)

You can explore a live deployment of this dataset, at this endpoint:
[https://features.byteroad.net/collections/sawcer](https://features.byteroad.net/collections/sawcer)
On this OpenAPI document, you can try all the available endpoints:
[https://features.byteroad.net/openapi?f=html](https://features.byteroad.net/openapi?f=html)