Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX Feature and FeatureCollection support to implement geometry normalization #4126

Merged
merged 6 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES_NEXT_RELEASE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
- Add: support for Feature and FeaturesCollection GeoJSON types in entity locations (#4114)
- Add: support for Feature and FeaturesCollection GeoJSON types (normalizing geometries) in entity locations (#4114)
- Add: support to null element in string list filters (e.g. q=A:foo,null) (#4120)
- Fix: allow limit=0 in all paginated operations in the NGSIv2 API (entities, entity types, subscriptions and registrations) (#1492)
- Add: conditions.alterationTypes subscription fuctionality (#1494)
Expand Down
23 changes: 16 additions & 7 deletions doc/manuals/user/ngsiv2_implementation_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,17 +230,26 @@ We have successfully tested the following types:
* MultiLineString
* Polygon
* MultiPolygon
* Feature (using the value of `geometry` field at the first level)

On the contrary, the following types doesn't work (you will get a "Database Error" if you try to use them):
More information on the tests conducted can be found [here](https://github.com/telefonicaid/fiware-orion/issues/3586).

* GeometryCollection
The types `Feature` and `FeatureCollection` are also supported, but in a special way. You can
use `Feature` or `FeatureCollection` to create/update `geo:json` attributes. However, when
the attribute value is retrieved (GET resposes or notifictaions) you will get only the content of:

With regards to `FeatureCollection`, it is supported only if it contains a single Feature (i.e.
the `features` field has only one element), in which case the value of `geometry` field of
such element is used.
* the `geometry` field, in the case of `Feature`
* the `geometry` field of the first item of the `features` array, in the case of `FeatureCollection`

More information on the tests conducted can be found [here](https://github.com/telefonicaid/fiware-orion/issues/3586).
Note that actually Orion stores the full value used at `Feature` or `FeatureCollection`
creation/updating time. However, from the point of view of normalization with other `geo:json` types,
it has been decided to return only the `geometry` part. In the future, maybe a flag to return
the full content would be implemented (more detail [in this issue](https://github.com/telefonicaid/fiware-orion/issues/4125)).

With regards to `FeatureCollection`, it is only accepted at creation/update time only if it contains a single
`Feature` (i.e. the `features` field has only one element). Otherwise , Orion would return an `BadRequest`error.

fgalan marked this conversation as resolved.
Show resolved Hide resolved
The only GeoJSON type not supported at all is `GeometryCollection`. You will get a "Database Error"
if you try to use them).

## Legacy attribute format in notifications

Expand Down
79 changes: 78 additions & 1 deletion src/lib/mongoBackend/location.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,79 @@ static bool stringArray2coords



/* ****************************************************************************
*
* getGeometryFromFeature -
*/
static orion::CompoundValueNode* getGeometryFromFeature(orion::CompoundValueNode* feature)
{
for (unsigned int ix = 0; ix < feature->childV.size(); ++ix)
{
orion::CompoundValueNode* childP = feature->childV[ix];
if (childP->name == "geometry")
{
return childP;
}
}

LM_E(("Runtime Error (geometry field expected in GeoJson Feature)"));
return NULL;
}



/* ****************************************************************************
*
* getGeometryFromFeatureCollection -
*/
static orion::CompoundValueNode* getGeometryFromFeatureCollection(orion::CompoundValueNode* featureCollection)
{
for (unsigned int ix = 0; ix < featureCollection->childV.size(); ++ix)
{
orion::CompoundValueNode* childP = featureCollection->childV[ix];
if (childP->name == "features")
{
return getGeometryFromFeature(featureCollection->childV[ix]->childV[0]);
}
}

LM_E(("Runtime Error (feature field expected in GeoJson FeatureCollection)"));
return NULL;
}



/* ****************************************************************************
*
* getGeometry -
*
* Get geometry compound value from attribute, taking into account special GeoJSON
* types such as Feature and FeatureCollection
*/
orion::CompoundValueNode* getGeometry(orion::CompoundValueNode* compoundValueP)
{
for (unsigned int ix = 0; ix < compoundValueP->childV.size(); ++ix)
{
orion::CompoundValueNode* childP = compoundValueP->childV[ix];
if ((childP->name == "type") && (childP->valueType == orion::ValueTypeString))
{
if (childP->stringValue == "Feature")
{
return getGeometryFromFeature(compoundValueP);
}
if (childP->stringValue == "FeatureCollection")
{
return getGeometryFromFeatureCollection(compoundValueP);
}
}
}

// Regular geo:json
return compoundValueP;
}



/* ****************************************************************************
*
* isFeatureType -
Expand Down Expand Up @@ -150,7 +223,11 @@ static bool isFeatureCollectionType(CompoundValueNode* featureCollection, orion:
}



/* ****************************************************************************
*
* isSpecialGeoJsonType -
*
*/
static bool isSpecialGeoJsonType(const ContextAttribute* caP, orion::BSONObjBuilder* geoJson, ApiVersion apiVersion)
{
if (caP->compoundValueP == NULL)
Expand Down
11 changes: 11 additions & 0 deletions src/lib/mongoBackend/location.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,15 @@ extern bool processLocationAtAppendAttribute
OrionError* oe
);



/* ****************************************************************************
*
* getGeometry -
*
* Get geometry compound value from attribute, taking into account special GeoJSON
* types such as Feature and FeatureCollection
*/
extern orion::CompoundValueNode* getGeometry(orion::CompoundValueNode* compoundValueP);

#endif // SRC_LIB_MONGOBACKEND_LOCATION_H_
14 changes: 13 additions & 1 deletion src/lib/ngsi/ContextAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "rest/OrionError.h"
#include "parse/CompoundValueNode.h"

#include "mongoBackend/location.h"
#include "mongoBackend/dbConstants.h"
#include "mongoBackend/dbFieldEncoding.h"
#include "mongoBackend/compoundValueBson.h"
Expand Down Expand Up @@ -959,7 +960,18 @@ std::string ContextAttribute::toJson(const std::vector<std::string>& metadataFi
//
if (compoundValueP != NULL)
{
jh.addRaw("value", compoundValueP->toJson());
orion::CompoundValueNode* childToRenderP = compoundValueP;
if (type == GEO_JSON)
{
childToRenderP = getGeometry(compoundValueP);
}

// Some internal error conditions in getGeometryToRender() (e.g. out of band manipulation
// of DB entities) may lead to NULL, so the check is needed
if (childToRenderP != NULL)
{
jh.addRaw("value", childToRenderP->toJson());
}
}
else if (valueType == orion::ValueTypeNumber)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ brokerStart CB

#
# 01. Create entity with geo:json using Feature type
# 02. Get entity and see Feature
# 02. Get entity and see Feature geometry
# 03. Geoquery covering the location returns the entity
# 04. Update entity with a regular geo:json Point
# 05. Get entity and see Point
# 06. Geoquery covering the location returns the entity
# 07. Update entity with a Feature again
# 08. Get entity and see Feature
# 08. Get entity and see Feature geometry
# 09. Geoquery covering the location returns the entity
#

Expand Down Expand Up @@ -68,8 +68,8 @@ echo
echo


echo "02. Get entity and see Feature"
echo "=============================="
echo "02. Get entity and see Feature geometry"
echo "======================================="
orionCurl --url /v2/entities/E
echo
echo
Expand Down Expand Up @@ -140,8 +140,8 @@ echo
echo


echo "08. Get entity and see Feature"
echo "=============================="
echo "08. Get entity and see Feature geometry"
echo "======================================="
orionCurl --url /v2/entities/E
echo
echo
Expand All @@ -165,10 +165,10 @@ Date: REGEX(.*)



02. Get entity and see Feature
==============================
02. Get entity and see Feature geometry
=======================================
HTTP/1.1 200 OK
Content-Length: 227
Content-Length: 133
Content-Type: application/json
Fiware-Correlator: REGEX([0-9a-f\-]{36})
Date: REGEX(.*)
Expand All @@ -179,17 +179,11 @@ Date: REGEX(.*)
"metadata": {},
"type": "geo:json",
"value": {
"geometry": {
"coordinates": [
-3.612711914,
40.539019781
],
"type": "Point"
},
"properties": {
"label": "-3.6127119138731127, 40.53901978067972"
},
"type": "Feature"
"coordinates": [
-3.612711914,
40.539019781
],
"type": "Point"
}
},
"type": "T"
Expand All @@ -199,7 +193,7 @@ Date: REGEX(.*)
03. Geoquery covering the location returns the entity
=====================================================
HTTP/1.1 200 OK
Content-Length: 229
Content-Length: 135
Content-Type: application/json
Fiware-Correlator: REGEX([0-9a-f\-]{36})
Date: REGEX(.*)
Expand All @@ -211,17 +205,11 @@ Date: REGEX(.*)
"metadata": {},
"type": "geo:json",
"value": {
"geometry": {
"coordinates": [
-3.612711914,
40.539019781
],
"type": "Point"
},
"properties": {
"label": "-3.6127119138731127, 40.53901978067972"
},
"type": "Feature"
"coordinates": [
-3.612711914,
40.539019781
],
"type": "Point"
}
},
"type": "T"
Expand Down Expand Up @@ -297,10 +285,10 @@ Date: REGEX(.*)



08. Get entity and see Feature
==============================
08. Get entity and see Feature geometry
=======================================
HTTP/1.1 200 OK
Content-Length: 229
Content-Length: 134
Content-Type: application/json
Fiware-Correlator: REGEX([0-9a-f\-]{36})
Date: REGEX(.*)
Expand All @@ -311,17 +299,11 @@ Date: REGEX(.*)
"metadata": {},
"type": "geo:json",
"value": {
"geometry": {
"coordinates": [
-23.612711914,
50.539019781
],
"type": "Point"
},
"properties": {
"label": "-23.6127119138731127, 50.53901978067972"
},
"type": "Feature"
"coordinates": [
-23.612711914,
50.539019781
],
"type": "Point"
}
},
"type": "T"
Expand All @@ -331,7 +313,7 @@ Date: REGEX(.*)
09. Geoquery covering the location returns the entity
=====================================================
HTTP/1.1 200 OK
Content-Length: 231
Content-Length: 136
Content-Type: application/json
Fiware-Correlator: REGEX([0-9a-f\-]{36})
Date: REGEX(.*)
Expand All @@ -343,17 +325,11 @@ Date: REGEX(.*)
"metadata": {},
"type": "geo:json",
"value": {
"geometry": {
"coordinates": [
-23.612711914,
50.539019781
],
"type": "Point"
},
"properties": {
"label": "-23.6127119138731127, 50.53901978067972"
},
"type": "Feature"
"coordinates": [
-23.612711914,
50.539019781
],
"type": "Point"
}
},
"type": "T"
Expand Down
Loading