Skip to content

Commit 521b780

Browse files
committed
feat(server): Add enclosed filtering to ListLocations
1 parent 5806af9 commit 521b780

5 files changed

Lines changed: 171 additions & 15 deletions

File tree

internal/server/postgres/dataserverimpl.go

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,19 +1608,20 @@ func (s *DataPlatformDataServiceServerImpl) ListLocations(
16081608

16091609
var locations []*pb.ListLocationsResponse_LocationSummary
16101610

1611-
if req.EnclosingLocationUuidFilter == nil {
1612-
lsprms := db.ListSourcesAtTimestampParams{
1613-
OauthID: req.UserOauthIdFilter,
1614-
GeometryUuids: parsedUuids,
1615-
AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
1616-
PermissionID: permissionId,
1617-
SourceTypeID: sourceTypeId,
1618-
GeometryTypeID: locationTypeId,
1611+
if req.EnclosingLocationUuidFilter != nil {
1612+
llprms := db.ListSourcesAtTimestampWithinParams{
1613+
OuterGeometryUuid: uuid.MustParse(*req.EnclosingLocationUuidFilter),
1614+
AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
1615+
OauthID: req.UserOauthIdFilter,
1616+
GeometryUuids: parsedUuids,
1617+
PermissionID: permissionId,
1618+
SourceTypeID: sourceTypeId,
1619+
GeometryTypeID: locationTypeId,
16191620
}
16201621

1621-
glResp, err := querier.ListSourcesAtTimestamp(ctx, lsprms)
1622+
glResp, err := querier.ListSourcesAtTimestampWithin(ctx, llprms)
16221623
if err != nil {
1623-
l.Err(err).Msgf("querier.ListSourcesAtTimestamp(%+v)", lsprms)
1624+
l.Err(err).Msgf("querier.ListSourcesAtTimestampWithin(%+v)", llprms)
16241625
} else {
16251626
for _, loc := range glResp {
16261627
metadata, err := jsonbToStruct(loc.SourceMetadata)
@@ -1647,9 +1648,9 @@ func (s *DataPlatformDataServiceServerImpl) ListLocations(
16471648
})
16481649
}
16491650
}
1650-
} else {
1651-
llprms := db.ListSourcesAtTimestampWithinParams{
1652-
OuterGeometryUuid: uuid.MustParse(*req.EnclosingLocationUuidFilter),
1651+
} else if req.EnclosedLocationUuidFilter != nil {
1652+
llprms := db.ListSourcesAtTimestampWithoutParams{
1653+
InnerGeometryUuid: uuid.MustParse(*req.EnclosedLocationUuidFilter),
16531654
AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
16541655
OauthID: req.UserOauthIdFilter,
16551656
GeometryUuids: parsedUuids,
@@ -1658,9 +1659,48 @@ func (s *DataPlatformDataServiceServerImpl) ListLocations(
16581659
GeometryTypeID: locationTypeId,
16591660
}
16601661

1661-
glResp, err := querier.ListSourcesAtTimestampWithin(ctx, llprms)
1662+
glResp, err := querier.ListSourcesAtTimestampWithout(ctx, llprms)
16621663
if err != nil {
1663-
l.Err(err).Msgf("querier.ListSourcesAtTimestampWithin(%+v)", llprms)
1664+
l.Err(err).Msgf("querier.ListSourcesAtTimestampWithout(%+v)", llprms)
1665+
} else {
1666+
for _, loc := range glResp {
1667+
metadata, err := jsonbToStruct(loc.SourceMetadata)
1668+
if err != nil {
1669+
l.Err(err).Msgf("jsonbToStruct(%s)", loc.SourceMetadata)
1670+
metadata = nil
1671+
}
1672+
1673+
locations = append(locations, &pb.ListLocationsResponse_LocationSummary{
1674+
LocationUuid: loc.GeometryUuid.String(),
1675+
LocationName: loc.GeometryName,
1676+
Latlng: &pb.LatLng{
1677+
Latitude: loc.Latitude,
1678+
Longitude: loc.Longitude,
1679+
},
1680+
EffectiveCapacityWatts: uint64(
1681+
loc.Capacity,
1682+
) * uint64(
1683+
math.Pow10(int(loc.CapacityUnitPrefixFactor)),
1684+
),
1685+
EnergySource: pb.EnergySource(loc.SourceTypeID),
1686+
LocationType: pb.LocationType(loc.GeometryTypeID),
1687+
Metadata: metadata,
1688+
})
1689+
}
1690+
}
1691+
} else {
1692+
lsprms := db.ListSourcesAtTimestampParams{
1693+
OauthID: req.UserOauthIdFilter,
1694+
GeometryUuids: parsedUuids,
1695+
AtTimestampUtc: pgtype.Timestamp{Time: time.Now().UTC(), Valid: true},
1696+
PermissionID: permissionId,
1697+
SourceTypeID: sourceTypeId,
1698+
GeometryTypeID: locationTypeId,
1699+
}
1700+
1701+
glResp, err := querier.ListSourcesAtTimestamp(ctx, lsprms)
1702+
if err != nil {
1703+
l.Err(err).Msgf("querier.ListSourcesAtTimestamp(%+v)", lsprms)
16641704
} else {
16651705
for _, loc := range glResp {
16661706
metadata, err := jsonbToStruct(loc.SourceMetadata)

internal/server/postgres/dataserverimpl_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,50 @@ func TestListLocationsLocationFilters(t *testing.T) {
944944
},
945945
expectedCount: 3,
946946
},
947+
{
948+
name: "Should filter by enclosing geometry and energy source",
949+
req: &pb.ListLocationsRequest{
950+
EnclosingLocationUuidFilter: &locationUuids[0],
951+
EnergySourceFilter: sourceFilter,
952+
LocationUuidsFilter: locationUuids,
953+
},
954+
expectedCount: 1,
955+
},
956+
{
957+
name: "Should filter by enclosing geometry and location type",
958+
req: &pb.ListLocationsRequest{
959+
EnclosingLocationUuidFilter: &locationUuids[0],
960+
LocationTypeFilter: typeFilter,
961+
LocationUuidsFilter: locationUuids,
962+
},
963+
expectedCount: 1,
964+
},
965+
{
966+
name: "Should filter by enclosed geometry",
967+
req: &pb.ListLocationsRequest{
968+
EnclosedLocationUuidFilter: &locationUuids[0],
969+
LocationUuidsFilter: locationUuids,
970+
},
971+
expectedCount: 3,
972+
},
973+
{
974+
name: "Should filter by enclosed geometry and energy source",
975+
req: &pb.ListLocationsRequest{
976+
EnclosedLocationUuidFilter: &locationUuids[0],
977+
EnergySourceFilter: sourceFilter,
978+
LocationUuidsFilter: locationUuids,
979+
},
980+
expectedCount: 1,
981+
},
982+
{
983+
name: "Should filter by enclosed geometry and location type",
984+
req: &pb.ListLocationsRequest{
985+
EnclosedLocationUuidFilter: &locationUuids[0],
986+
LocationTypeFilter: typeFilter,
987+
LocationUuidsFilter: locationUuids,
988+
},
989+
expectedCount: 1,
990+
},
947991
{
948992
name: "Should return nothing for enclosing geometry containing nothing",
949993
req: &pb.ListLocationsRequest{

internal/server/postgres/sql/migrations/00002_locations.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ SELECT
145145
sh.capacity_limit_sip,
146146
sh.metadata,
147147
g.geometry_name,
148+
g.geometry_type_id,
148149
ST_X(g.centroid)::REAL AS longitude,
149150
ST_Y(g.centroid)::REAL AS latitude,
150151
TSRANGE(

internal/server/postgres/sql/queries/locations.sql

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,62 @@ WHERE
222222
AND (sqlc.narg(geometry_type_id)::SMALLINT IS NULL OR us.geometry_type_id = sqlc.narg(geometry_type_id)::SMALLINT)
223223
AND (sqlc.narg(oauth_id)::TEXT IS NULL OR us.oauth_id = sqlc.arg(oauth_id)::TEXT)
224224
AND (sqlc.narg(permission_id)::SMALLINT IS NULL OR us.permission_id = sqlc.narg(permission_id)::SMALLINT);
225+
226+
-- name: ListSourcesAtTimestampWithout :many
227+
/* ListSourcesAtTimestampWithout returns all sources that match the given filters
228+
* and contain a given geometry.
229+
* This has to be seperated from the ListSourcesAtTimestamp query due to the spatial join.
230+
*/
231+
WITH containing_geometries AS (
232+
SELECT
233+
l.geometry_uuid,
234+
l.geometry_name,
235+
l.geometry_type_id,
236+
l.geom,
237+
l.centroid,
238+
l.metadata
239+
FROM loc.geometries AS l
240+
INNER JOIN
241+
loc.geometries AS l_inner ON ST_WITHIN(
242+
l_inner.geom,
243+
l.geom
244+
) AND l_inner.geometry_uuid = sqlc.arg(inner_geometry_uuid)::UUID
245+
AND l.geometry_uuid <> sqlc.arg(inner_geometry_uuid)::UUID
246+
),
247+
unfiltered_sources AS (
248+
SELECT
249+
u.oauth_id,
250+
lp.permission_id,
251+
ls.source_type_id,
252+
ls.geometry_uuid,
253+
ls.capacity,
254+
ls.capacity_unit_prefix_factor,
255+
ls.capacity_limit_sip,
256+
l.geometry_name,
257+
l.geometry_type_id,
258+
ST_X(l.centroid)::REAL AS longitude,
259+
ST_Y(l.centroid)::REAL AS latitude,
260+
l.metadata AS geometry_metadata,
261+
ls.metadata AS source_metadata
262+
FROM loc.sources_mv AS ls
263+
INNER JOIN containing_geometries AS l USING (geometry_uuid)
264+
LEFT OUTER JOIN iam.location_policies AS lp USING (geometry_uuid, source_type_id)
265+
LEFT OUTER JOIN iam.org_location_policy_groups USING (location_policy_group_uuid)
266+
LEFT OUTER JOIN iam.users AS u USING (org_uuid)
267+
WHERE ls.sys_period @> sqlc.arg(at_timestamp_utc)::TIMESTAMP
268+
)
269+
SELECT
270+
*,
271+
COALESCE(
272+
us.capacity_limit_sip::REAL * us.capacity / 30000.0, us.capacity::REAL
273+
)::REAL AS capacity_inc_limit
274+
FROM unfiltered_sources AS us
275+
WHERE
276+
(sqlc.narg(source_type_id)::SMALLINT IS NULL OR us.source_type_id = sqlc.narg(source_type_id)::SMALLINT)
277+
AND (
278+
ARRAY_LENGTH(sqlc.arg(geometry_uuids)::UUID [], 1) IS NULL
279+
OR us.geometry_uuid = ANY(sqlc.arg(geometry_uuids)::UUID [])
280+
)
281+
AND (sqlc.narg(geometry_type_id)::SMALLINT IS NULL OR us.geometry_type_id = sqlc.narg(geometry_type_id)::SMALLINT)
282+
AND (sqlc.narg(oauth_id)::TEXT IS NULL OR us.oauth_id = sqlc.arg(oauth_id)::TEXT)
283+
AND (sqlc.narg(permission_id)::SMALLINT IS NULL OR us.permission_id = sqlc.narg(permission_id)::SMALLINT);

proto/ocf/dp/dp-data.messages.proto

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,18 @@ message ListLocationsRequest {
599599
optional string enclosing_location_uuid_filter = 6 [
600600
(buf.validate.field).string.uuid = true
601601
];
602+
// Optional filter to only return locations that enclose a specific location.
603+
optional string enclosed_location_uuid_filter = 7 [
604+
(buf.validate.field).string.uuid = true
605+
];
606+
607+
608+
option (buf.validate.message).cel = {
609+
id: "enclosing_and_enclosed_mutually_exclusive"
610+
message: "enclosing_location_uuid_filter and enclosed_location_uuid_filter cannot both be set"
611+
expression:
612+
"!(has(this.enclosing_location_uuid_filter) && has(this.enclosed_location_uuid_filter))"
613+
};
602614
}
603615

604616
message ListLocationsResponse {

0 commit comments

Comments
 (0)