Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Note

GraphQL API v1 is going to be deprecated in 2024Q3. If you plan to use GraphQL API, please, contact nabrosimov@nwave.io to get GraphQL API v2 documentation.

Table of Contents

GraphQL Occupancy API offers similar functionality to REST Occupancy API, however, there are additional benefits to using GraphQL.

You can select the fields you want to receive from the API. By requesting only the required fields from the API you can decrease traffic and consequently increase load speed.

Additionally, GraphQL Occupancy API supports subscriptions, which allows you to receive real-time occupancy updates.

Authorization

In order to authorize with this API, you need to add the following header to your request:

Key

Value

Authorization

<your token>

Your token can be obtained from the Company Info section of the Nwave’s console.

Note

You are able to call API not more frequent, than 300 times per 5 minutes interval with use of the same token. If amount of requests exceeds the limit, API reponds with error until the end of 5 minutes interval.

GraphQL Schema

The following GraphQL schema describes data types and operations that can be performed.

Expand
titleschema.graphql
Code Block
languagegraphql
type GroupOccupancy @aws_api_key
@aws_lambda {
	id: ID!
	zoneId: Int
	levelId: Int
	name: String!
	groupType: String!
	customId: String
	location: Location
	positionsOccupancy: [PositionOccupancy]
	summary: OccupancySummary
}

input GroupOccupancyInput {
	id: ID!
	zoneId: Int
	levelId: Int
	name: String!
	groupType: String!
	customId: String
	location: LocationInput!
	positionsOccupancy: [PositionOccupancyInput!]!
	summary: OccupancySummaryInput!
}

type LevelOccupancy @aws_api_key
@aws_lambda {
	id: Int
	name: String
	zoneId: Int!
	floorNumber: Int
	summary: OccupancySummary
}

input LevelOccupancyInput {
	id: Int
	zoneId: Int!
	floorNumber: Int
	name: String
	summary: OccupancySummaryInput!
}

type Location @aws_api_key
@aws_lambda {
	lat: Float!
	lon: Float!
}

input LocationInput {
	lat: Float!
	lon: Float!
}

enum OccupancyStatus {
	Occupied
	Free
}

type OccupancySummary @aws_api_key
@aws_lambda {
	total: Int!
	occupied: Int!
	available: Int!
	undefined: Int!
}

input OccupancySummaryInput {
	total: Int!
	occupied: Int!
	available: Int!
	undefined: Int!
}

type PositionOccupancy @aws_api_key
@aws_lambda {
	id: ID!
	customId: String
	groupId: Int
	occupancyStatus: OccupancyStatus
	statusChangeTime: AWSDateTime
	location: Location!
	labels: [String]
}

input PositionOccupancyInput {
	id: ID!
	customId: String
	groupId: Int!
	occupancyStatus: OccupancyStatus!
	statusChangeTime: AWSDateTime!
	location: LocationInput!
	labels: [String]
}

union RtaUpdateObject = ZoneOccupancy | LevelOccupancy | GroupOccupancy | PositionOccupancy 

interface SubscriptionArea {
	id: ID!
	location: Location
	radius: Int
	zoneId: [Int]
	levelId: [Int]
	floorNumber: [Int]
	groupId: [Int]
	labels: [String]
	granularity: SubscriptionGranularity!
	expiresOn: AWSDateTime!
}

type SubscriptionAreaNoUpdates implements SubscriptionArea @aws_api_key
@aws_lambda {
	id: ID!
	location: Location
	radius: Int
	zoneId: [Int]
	levelId: [Int]
	floorNumber: [Int]
	groupId: [Int]
	labels: [String]
	granularity: SubscriptionGranularity!
	expiresOn: AWSDateTime!
}

type SubscriptionAreaWithUpdates implements SubscriptionArea @aws_api_key
@aws_lambda {
	id: ID!
	location: Location
	radius: Int
	zoneId: [Int]
	levelId: [Int]
	floorNumber: [Int]
	groupId: [Int]
	labels: [String]
	granularity: SubscriptionGranularity!
	expiresOn: AWSDateTime!
	updates: [RtaUpdateObject]
	updateTime: AWSDateTime
}

enum SubscriptionGranularity {
	Position
	Level
	Group
	Zone
}

type ZoneOccupancy @aws_api_key
@aws_lambda {
	id: ID!
	name: String!
	projectId: Int
	summary: OccupancySummary
}

input ZoneOccupancyInput {
	id: ID!
	name: String!
	projectId: Int
	summary: OccupancySummaryInput!
}

type Mutation {
	createSubscriptionArea(
		lat: Float,
		lon: Float,
		radius: Int,
		zoneId: [Int],
		levelId: [Int],
		floorNumber: [Int],
		groupId: [Int],
		labels: [String],
		granularity: SubscriptionGranularity
	): SubscriptionAreaNoUpdates
		@aws_api_key
@aws_lambda
	updateSubscriptionArea(
		id: ID!,
		lat: Float,
		lon: Float,
		radius: Int,
		zoneId: [Int],
		levelId: [Int],
		floorNumber: [Int],
		groupId: [Int],
		labels: [String],
		granularity: SubscriptionGranularity
	): SubscriptionAreaNoUpdates
		@aws_api_key
@aws_lambda
	updatePositionOccupancy(positionId: Int!, occupancyStatus: OccupancyStatus!, timestamp: String!): PositionOccupancy
		@aws_api_key
	pushPositionUpdates(
		id: ID!,
		location: LocationInput,
		radius: Int,
		zoneId: [Int],
		levelId: [Int],
		floorNumber: [Int],
		groupId: [Int],
		labels: [String],
		granularity: SubscriptionGranularity!,
		expiresOn: AWSDateTime!,
		updates: [PositionOccupancyInput!]!,
		updateTime: String!
	): SubscriptionAreaWithUpdates
		@aws_api_key
	pushGroupUpdates(
		id: ID!,
		location: LocationInput,
		radius: Int,
		zoneId: [Int],
		levelId: [Int],
		floorNumber: [Int],
		groupId: [Int],
		labels: [String],
		granularity: SubscriptionGranularity!,
		expiresOn: AWSDateTime!,
		updates: [GroupOccupancyInput!]!,
		updateTime: String!
	): SubscriptionAreaWithUpdates
		@aws_api_key
	pushLevelUpdates(
		id: ID!,
		location: LocationInput,
		radius: Int,
		zoneId: [Int],
		levelId: [Int],
		floorNumber: [Int],
		groupId: [Int],
		labels: [String],
		granularity: SubscriptionGranularity!,
		expiresOn: AWSDateTime!,
		updates: [LevelOccupancyInput!]!,
		updateTime: String!
	): SubscriptionAreaWithUpdates
		@aws_api_key
	pushZoneUpdates(
		id: ID!,
		location: LocationInput,
		radius: Int,
		zoneId: [Int],
		levelId: [Int],
		floorNumber: [Int],
		groupId: [Int],
		labels: [String],
		granularity: SubscriptionGranularity!,
		expiresOn: AWSDateTime!,
		updates: [ZoneOccupancyInput!]!,
		updateTime: String!
	): SubscriptionAreaWithUpdates
		@aws_api_key
}

type Query {
	groupOccupancy(id: ID!): GroupOccupancy
		@aws_api_key
@aws_lambda
	findGroupOccupancies(
		ids: [Int],
		lat: Float,
		lon: Float,
		radius: Int,
		levelId: [Int!],
		labels: [String!],
		floorNumber: [Int!],
		zoneId: [Int!],
		projectId: Int,
		groupCustomId: String,
		limit: Int,
		offset: Int
	): [GroupOccupancy]
		@aws_api_key
@aws_lambda
	findPositionOccupancies(
		ids: [Int],
		lat: Float,
		lon: Float,
		radius: Int,
		groupId: [Int!],
		levelId: [Int!],
		labels: [String!],
		floorNumber: [Int!],
		zoneId: [Int!],
		projectId: Int,
		groupCustomId: String,
		limit: Int,
		offset: Int
	): [PositionOccupancy]
		@aws_api_key
@aws_lambda
}

type Subscription {
	onSubscriptionAreaUpdates(id: ID!): SubscriptionAreaWithUpdates
		@aws_api_key
@aws_lambda
@aws_subscribe(mutations: ["pushPositionUpdates","pushGroupUpdates","pushLevelUpdates","pushZoneUpdates"])
}

# @api_key (admin) auth and @aws_lambda (user) auth are required for all types apart from:
##   updatePositionOccupancy
##   pushPositionUpdates
##   pushGroupUpdates
##   pushLevelUpdates
##   pushZoneUpdates
## These are for internal subscription notification and should have exclusive admin access
schema {
	query: Query
	mutation: Mutation
	subscription: Subscription
}

Subscription Areas

A subscription area is an object that has two main functions:

  1. Filtering of the incoming position updates

  2. Defining the format of updates that is received by subscribers

There are two types defined in the GraphQL schema for Subscription Areas:

SubscriptionAreaNoUpdates - this type is returned when you create or update a Subscription Area. It does not have ‘updates’ & ‘updateTime’ fields as updates are only returned to active subscriptions.

Info

Subscription areas expiry 5 minutes after creation. Any update of a subscription area, including an empty one, will extend the expiration time by 5 minutes from the time of update.

SubscriptionAreaWithUpdates - this type is returned to subscribers and as the name suggests it contains the ‘updates’ & ‘updateTime’ fields.

Subscription Area Filters

Subscription Areas filter the incoming updates and only notify the subscribers if the incoming position update matches all of the filters. Subscription Area filters can also be split into 2 categories:

  1. Hierarchical Filters: zoneId, groupId, levelId, floorNumber, labels

  2. Geospatial Filters: lat, lon, radius

Info

A valid subscription area must have at least one hierarchical filter and/or all of the geospatial filters.

A match is when all position attributes are a subset (⊆) of the Subscription Area filters and it implies that a given position is inside a Subscription Area.

Subscription Area filter with null value matches everything.

The following example is a valid match between Subscription Area and Position Update.

Position

  1. zoneId: 1

  2. groupId: 2

  3. levelId: 3

  4. floorNumber: 4

  5. labels: ['EV', ‘Disabled’]

  6. lat: 0.0

  7. lon: 0.0

Subscription Area

  1. zoneId: [1, 2, 3]

  2. groupId: [1, 2, 3]

  3. levelId: [3]

  4. floorNumber: null

  5. lables: ['EV', ‘Disabled’, ‘VIP’]

  6. lat: 0.0

  7. lon: 0.0

  8. radius: 100

Note

Geospatial filters create a circular search area on the map, and there can be cases when a group of devices partially falls into the search area. (e.g. Half a group is within the search area and half is outside of the search area).

Updates for positions that do not fall into the search area would still notify the subscribers.

Subscription Area Updates

The updates field of a Subscription Area can contain a list of 4 different types. All types within the updates list are the same. The type you will receive in the updates field depends on the granularity you choose.

Granularity

Updates Type

Zone

ZoneOccupancy

Level

LevelOccupancy

Group

GroupOccupancy

Position

PositionOccupancy

Info

Updates field will always contain only the object that has changed and therfore the list should always have a length of 1.

Object & Field Descriptions

SubscirptionArea

  • id - subscription area id

  • location - center coordinates of the search area

  • radius - radius from the center in meeters that creates the search area

  • zoneId - list of zone ids to match

  • levelId - list of level ids to match

  • floorNumber - list of floor numbers to match

  • groupId - list of group ids to match

  • labels - list of labels to match

  • granularity - defines the updates type

  • expiresOn - timestamp of subscription area expiration

  • udpates - occupancy updates for a subscription area

  • updateTime - timestamp of the occupancy update

ZoneOccupancy

  • id - zone id

  • name - zone name

  • projectId - project id

  • summary - summary of occupancies in the Zone

LevelOccupancy

  • id - level id

  • name - level name

  • zoneId - zone id

  • floorNumber - floor number of a level

  • summary - summary of occupancies on a Level

GroupOccupancy

  • id - group id

  • zoneId - zone id

  • levelId - level id

  • name - group name

  • groupType - group type e.g. marked_bay

  • customId - user defined group id

  • location - сenter coordinates of a group

  • positionOccupancy - list of PositionOccupancy objects for a group

  • summary - summary of occupancies in the Group

PositionOccupancy

  • id - position id

  • customId - custom id

  • groupId - group id

  • occupancyStatus - ‘occupied’, ‘free’ or null

  • statusChangeTime - timestamp of last occupancyStatus change

  • location: coordinates of a position

Info

The summary object should be used to display availability for all parking group types as it handles unmarked bay occupancies

Operations

The following operations can be performed using either Postman or curl command except for subscription operations.

groupOccupancy

This query will return the occupancy of a single group.

Query

Code Block
languagegraphql
query MyQuery {
  groupOccupancy(id: 123) {
    id
    customId
    name
    location {
      lat
      lon
    }
    positionsOccupancy {
      customId
      id
      groupId
      labels
      location {
        lat
        lon
      }
      occupancyStatus
      statusChangeTime
    }
  }
}

Response

Code Block
languagejson
{
  "data": {
    "groupOccupancy": {
      "id": 3544,
      "customId": null,
      "name": "Foo",
      "location": {
        "lat": 51.493601937687224,
        "lon": -0.12852029611716095
      },
      "positionsOccupancy": [
        {
          "customId": "",
          "id": 1,
          "groupId": 123,
          "labels": null,
          "location": {
            "lat": 51.49360068522545,
            "lon": -0.1286061268139623
          },
          "occupancyStatus": "Free",
          "statusChangeTime": "2021-01-27T19:22:47.548+00:00"
        },
        {
          "customId": "",
          "id": 2,
          "groupId": 123,
          "labels": null,
          "location": {
            "lat": 51.493601937687224,
            "lon": -0.12852029611716098
          },
          "occupancyStatus": "Occupoed",
          "statusChangeTime": "2021-01-27T18:56:53.502+00:00"
        }
      ]
    }
  }
}

findGroupOccupancies

This query will return a list of group occupancies.

Query

Code Block
languagegraphql
query MyQuery {
  findGroupOccupancies(ids: [1234]) {
    id
    location {
      lat
      lon
    }
    summary {
      available
      occupied
      total
      undefined
    }
  }
}

 

Response

Code Block
languagejson
{
  "data": {
    "findGroupOccupancies": [
      {
        "id": 1234,
        "location": {
          "lat": 50.780416909504915,
          "lon": -1.0914407438684124
        },
        "summary": {
          "available": 34,
          "occupied": 13,
          "total": 47,
          "undefined": 0
        }
      }
    ]
  }
}

findPositionOccupancies

This query will return a list of position occupancies.

Query

Code Block
languagegraphql
query MyQuery {
  findPositionOccupancies(ids: [1]) {
    id
    labels
    location {
      lat
      lon
    }
    occupancyStatus
    statusChangeTime
    groupId
    customId
  }
}

 

Response

Code Block
languagejson
{
  "data": {
    "findPositionOccupancies": [
      {
        "id": 12345,
        "labels":["EV", "VIP"],
        "location": {
          "lat": 51.49360068522545,
          "lon": -0.1286061268139623
        },
        "occupancyStatus": "Free",
        "statusChangeTime": "2021-01-27T19:22:47.548+00:00",
        "groupId": 123,
        "customId": ""
      }
    ]
  }
}

createSubscriptionArea

This mutation will create a new subscription area.

Subscription Area Expiration

By default, subscription areas expire 5 minutes after creation. Once a subscription area is expired, it cannot be extended and subscribers will stop receiving updates.

Mutation

Code Block
languagegraphql
mutation MyMutation {
  createSubscriptionArea(
    granularity: Group, 
    lat: 51.493930, 
    lon: -0.129030, 
    radius: 100,
    labels: ["EV"]
  ) {
    expiresOn
    granularity
    id
    location {
      lat
      lon
    }
    radius
  }
}

 

Response

Code Block
languagejson
{
  "data": {
    "createSubscriptionArea": {
      "expiresOn": "2021-01-28T23:20:06.232+00:00",
      "granularity": "Group",
      "id": 1,
      "location": {
        "lat": 51.49393,
        "lon": -0.12903
      },
      "radius": 100
    }
  }
}

onSubscriptionAreaUpdates

This subscription will subscribe to occupancy updates inside an area.

Selected updates object type must correspond to the granularity of the created search area:

  1. granularity: Position → updates { … on PositionOccupancy }

  2. granularity: Group → updates { … on GroupOccupancy }

If you are unsure of the granularity for your subscription area, you can specify both types inside the updates.

Subscription

Code Block
languagegraphql
subscription MySubscription {
  onSubscriptionAreaUpdates(id: 1) {
    id
    expiresOn
    granularity
    location {
      lat
      lon
    }
    radius
    updateTime
    updates {
      ... on GroupOccupancy {
        id
        name
        customId
        location {
          lat
          lon
        }
        positionsOccupancy {
          customId
          groupId
          id
          location {
            lat
            lon
          }
          occupancyStatus
          statusChangeTime
          labels
        }
        summary {
          undefined
          total
          occupied
          available
        }
      }
    }
  }
}

 

Update

Code Block
languagejson
{
  "data": {
    "onSubscriptionAreaUpdates": {
      "id": 1,
      "expiresOn": "2021-01-28T23:20:06.232+00:00",
      "granularity": "Group",
      "location": {
        "lat": 51.49393,
        "lon": -0.12903
      },
      "radius": 1000,
      "updateTime": "2021-01-28T23:17:38.400+00:00",
      "updates": [
        {
          "id": 123,
          "name": "Foo",
          "customId": null,
          "location": {
            "lat": 51.493601937687224,
            "lon": -0.12852029611716095
          },
          "positionsOccupancy": [
            {
              "customId": "",
              "groupId": 123,
              "id": 1,
              "location": {
                "lat": 51.49360068522545,
                "lon": -0.1286061268139623
              },
              "occupancyStatus": "Occupied",
              "statusChangeTime": "2021-01-28T23:17:38.400+00:00"
              "labels": null
            }
          ],
          "summary": {
            "undefined": 0,
            "total": 1,
            "occupied": 1,
            "available": 0
          }
        }
      ]
    }
  }
}

updateSubscriptionArea

This mutation will update an existing subscription area.

Info

All subscription area updates will extend expiration by 5 minutes from the current time.

You can also send empty mutation to only update the expiration.

Mutation

Code Block
languagegraphql
mutation MyMutation {
  updateSubscriptionArea(
    id: 1, 
    granularity: Position, 
    radius: 500
  ) {
    expiresOn
    id
    granularity
    location {
      lat
      lon
    }
    radius
  }
}

 

Code Block
languagegraphql
mutation ExtendSubscription {
  updateSubscriptionArea(
    id: 1, 
  ) {
    expiresOn
    id
  }
}

Response

Code Block
languagejson
{
  "data": {
    "updateSubscriptionArea": {
      "expiresOn": "2021-01-28T23:20:06.232134+00:00",
      "id": 1,
      "granularity": "Position",
      "location": {
        "lat": 51.49393,
        "lon": -0.12903
      },
      "radius": 500
    }
  }
}

Code Block
languagejson
{
  "data": {
    "updateSubscriptionArea": {
      "expiresOn": "2021-01-28T23:25:06.232134+00:00",
      "id": 1,
    }
  }
}

Use cases

Query

Use case

Code Block
languagegraphql
query MyQuery {
  findGroupOccupancies(
    lat: 0.0, 
    lon: 0.0, 
    radius: 2000
  ) {
    location {
      lat
      lon
    }
    summary {
      available
    }
  }
}
Info

The summary object should be used to display availability for all parking group types

Displaying all parking groups within 2km in a mobile app

Code Block
languagegraphql
query MyQuery {
  findGroupOccupancies(zoneId: [1607]) {
    id
    name
    positionsOccupancy {
      occupancyStatus
      labels
    }
    summary {
      available
      occupied
      total
      undefined
    }
  }
}

Getting group occupancy summary and each position status and labels to be able to calculate occupancy statistics by labels using only one query

Code Block
languagegraphql
query MyQuery {
  groupOccupancy(id: 1) {
    positionsOccupancy {
      location {
        lat
        lon
      }
      occupancyStatus
    }
  }
}

Displaying individual parking positions when a user approaches his desired parking location

Code Block
languagegraphql
query MyQuery {
  findPositionOccupancies(
    lat: 0.0, 
    lon: 0.0, 
    radius: 2000,
    labels: ['Disabled']
  ) {
    id
    location {
      lat
      lon
    }
    occupancyStatus
  }
}

Displaying occupancy statuses of positions with label “Disabled” within 2km radius

Code Block
languagegraphql
mutation MyMutation {
  createSubscriptionArea(
    granularity: Position, 
    lat: 1.5, 
    lon: 1.5, 
    radius: 2000
  ) {
    id
  }
}
subscription MySubscription {
  onSubscriptionAreaUpdates(
  id: <id from previous mutation>
  )
}

Displaying live parking statuses within 2km radius

Code Block
languagegraphql
mutation LevelOccuppancyMutation {
  createSubscriptionArea(
    zoneId: 3, 
    granularity: Level
  ) {
    id
  }
}
subscription MySubscription {
  onSubscriptionAreaUpdates(
  id: <id from previous mutation>
  )
}

Displaying live occupancies per level in a zone (multistory car park)

Code Block
languagegraphql
mutation LevelOccuppancyDisabledMutation {
  createSubscriptionArea(
    zoneId: 3, 
    labels: "Disabled",
    granularity: Level
  ) {
    id
  }
}
subscription MySubscription {
  onSubscriptionAreaUpdates(
  id: <id from previous mutation>
  )
}

Displaying live occupancies of disabled spaces per level in a zone

Code Block
languagegraphql
mutation MyMutation {
  updateSubscriptionArea(
    id: 1, 
  ) {
    expiresOn
    id
  }
}

Extend subscription area expiration

Postman Collection

Download Postman Collection here:

View file
nameGraphQL Occupancy.postman_collection.json

Importing Collection

  1. Click import in My Workspace section.

  1. Upload the collection file and click

Import.

Adding API key to Postman Environment

This collection uses the api_key variable to add an authorization token to the x-api-key header.

  1. To create a new environment click on the eye icon near the top right corner.

2. Click Add to add a new environment.

3. Add the gql_api_key variable name and your token in the initial value.

4. Select the New Environment from the list.

5. You can now test the requests in the GraphQL Occupancy Collection.

Example Python Client

There is a simple GraphQL client example in our Github repository. It is developed in Python and let you start getting data on demand and subscribe on occupancy change notifications.

Please, follow the instructions in the README.md to prepare your invironment for running example.

https://github.com/nwaveio/graphql_occupancy_example