Geoqueries
A geoquery selects entities by where they are, whether inside an area, near a point, or crossing a line. You add it to an ordinary query as three parameters, and it runs against a GeoProperty of each entity. That is location unless you name another. This guide shows how to write one, how to combine it with other filters, and what the broker refuses.
The curl examples pass -g, so that curl doesn't read the brackets in coordinates as a range. In Rust the reference shape is a typed Shape, and the geoquery is built with geoquery::near_max, geoquery::within and the other start functions, so a ring that isn't closed is refused by try_new before any request leaves the machine.
Writing a geoquery
A geoquery needs three parameters, and they go together. georel says how the entity's geometry must relate to the reference, geometry names the reference shape's GeoJSON type, and coordinates gives its positions.
The first asks for everything within 2 km of a point.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Building&georel=near;maxDistance==2000&geometry=Point&coordinates=[2.3522,48.8566]
curl -i -g 'http://localhost:1056/ngsi-ld/v1/entities?type=Building&georel=near;maxDistance==2000&geometry=Point&coordinates=[2.3522,48.8566]'
let page = client
.entities()
.query()
.entity_type("Building")
.geo_q(geoquery::near_max(2000, point))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Building",
"georel": "near;maxDistance==2000",
"geometry": "Point",
"coordinates": POINT,
},
)
assert response.status_code == 200
The next asks for everything inside a polygon.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Building&georel=within&geometry=Polygon&coordinates=[[[2.3,48.84],[2.38,48.84],[2.38,48.88],[2.3,48.88],[2.3,48.84]]]
curl -i -g 'http://localhost:1056/ngsi-ld/v1/entities?type=Building&georel=within&geometry=Polygon&coordinates=[[[2.3,48.84],[2.38,48.84],[2.38,48.88],[2.3,48.88],[2.3,48.84]]]'
let page = client
.entities()
.query()
.entity_type("Building")
.geo_q(geoquery::within(area))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Building",
"georel": "within",
"geometry": "Polygon",
"coordinates": AREA,
},
)
assert response.status_code == 200
A polygon's ring is closed: its last position repeats the first.
The relations
Each georel tests the entity's geometry against the reference.
near;maxDistance==dcomes withindmetres of the reference.near;minDistance==dstays at leastdmetres away.withinlies inside the reference.containscontains the reference.intersectsshares at least one point with the reference.disjointshares no point with the reference.equalsis the same shape as the reference.overlapsshares part of its interior, with the same dimension.
near takes exactly one of the two distances. A band with both needs the near distance band extension.
Distances run over the earth's surface, between the nearest points of the two geometries, not between their centres. A building whose nearest wall is 100 m from the point is 100 m away, however large it is. Geometry and geoqueries gives the details.
Choosing the attribute
geoproperty names the GeoProperty to test.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Vehicle&geoproperty=operationSpace&georel=intersects&geometry=Point&coordinates=[2.3522,48.8566]
curl -i -g 'http://localhost:1056/ngsi-ld/v1/entities?type=Vehicle&geoproperty=operationSpace&georel=intersects&geometry=Point&coordinates=[2.3522,48.8566]'
let page = client
.entities()
.query()
.entity_type("Vehicle")
.geo_q(geoquery::intersects(point).geoproperty("operationSpace"))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Vehicle",
"geoproperty": "operationSpace",
"georel": "intersects",
"geometry": "Point",
"coordinates": POINT,
},
)
assert response.status_code == 200
The name is expanded against the @context like any attribute name. An entity without that attribute doesn't match. If the attribute has several instances, one matching instance is enough.
Combining with other filters
A geoquery is one more condition, and every condition has to hold.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Vehicle&q=speed>30&georel=within&geometry=Polygon&coordinates=[[[2.3,48.84],[2.38,48.84],[2.38,48.88],[2.3,48.88],[2.3,48.84]]]
curl -i -g 'http://localhost:1056/ngsi-ld/v1/entities?type=Vehicle&q=speed>30&georel=within&geometry=Polygon&coordinates=[[[2.3,48.84],[2.38,48.84],[2.38,48.88],[2.3,48.88],[2.3,48.84]]]'
let page = client
.entities()
.query()
.entity_type("Vehicle")
.q(q::attr("speed").gt(30))
.geo_q(geoquery::within(area))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Vehicle",
"q": "speed>30",
"georel": "within",
"geometry": "Polygon",
"coordinates": AREA,
},
)
assert response.status_code == 200
In a query sent as a body to POST /ngsi-ld/v1/entityOperations/query, the three parameters go into a geoQ object.
- http
- curl
- rust
- python
POST /ngsi-ld/v1/entityOperations/query
Content-Type: application/json
{
"type": "Query",
"entities": [
{
"type": "Building"
}
],
"geoQ": {
"georel": "near;maxDistance==1000",
"geometry": "Point",
"coordinates": [
2.2945,
48.8584
]
}
}
curl -i http://localhost:1056/ngsi-ld/v1/entityOperations/query \
-H 'Content-Type: application/json' \
-d '{
"type": "Query",
"entities": [
{
"type": "Building"
}
],
"geoQ": {
"georel": "near;maxDistance==1000",
"geometry": "Point",
"coordinates": [
2.2945,
48.8584
]
}
}'
let tower = Shape::Point(Position::try_new(2.2945, 48.8584)?);
let page = client
.entities()
.query()
.entity_type("Building")
.geo_q(geoquery::near_max(1000, tower))
.method(QueryMethod::Post)
.send()
.await?;
response = requests.post(
f"{BASE}/entityOperations/query",
json={
"type": "Query",
"entities": [
{
"type": "Building",
},
],
"geoQ": {
"georel": "near;maxDistance==1000",
"geometry": "Point",
"coordinates": [
2.2945,
48.8584,
],
},
},
)
assert response.status_code == 200
A subscription takes the same geoQ. It then notifies only about entities that match it. Subscriptions and notifications covers the filters.
Sorting by distance
To get the nearest entities first, order by distance from a point with orderFrom. Ordering explains the distance sort and orderFrom.
Getting GeoJSON back
Send Accept: application/geo+json and the answer is a GeoJSON FeatureCollection, one feature per entity, ready for a map.
- http
- curl
- rust
- python
GET /ngsi-ld/v1/entities?type=Building&georel=within&geometry=Polygon&coordinates=[[[2.3,48.84],[2.38,48.84],[2.38,48.88],[2.3,48.88],[2.3,48.84]]]
Accept: application/geo+json
curl -i -g 'http://localhost:1056/ngsi-ld/v1/entities?type=Building&georel=within&geometry=Polygon&coordinates=[[[2.3,48.84],[2.38,48.84],[2.38,48.88],[2.3,48.88],[2.3,48.84]]]' \
-H 'Accept: application/geo+json'
let features = client
.entities()
.query_geo_json()
.entity_type("Building")
.geo_q(geoquery::within(area))
.send()
.await?;
response = requests.get(
f"{BASE}/entities",
params={
"type": "Building",
"georel": "within",
"geometry": "Polygon",
"coordinates": AREA,
},
headers={"Accept": "application/geo+json"},
)
assert response.status_code == 200
Each feature's geometry comes from location, or from the GeoProperty that geometryProperty names. geometryProperty works only with this Accept.
Altitude
A position can carry a third ordinate, the altitude. Andromeda stores it and gives it back, but a geoquery looks only at the footprint on the ground. Two entities at the same longitude and latitude are in the same place, whatever their height. Altitude has the rules for mixing flat and elevated positions.
What gets refused
400BadRequestDatafor an incomplete geoquery, sincegeorel,geometryandcoordinatesgo together. Send all three or none.400BadRequestDatafornearwithout a distance, or with both distances. Send exactly one ofmaxDistanceandminDistance.400BadRequestDatafor a malformed geometry, where the coordinates don't fit the geometry type, a ring isn't closed, or the type isGeometryCollection, which isn't supported. Strictness lists the geometry rules.