Skip to content

Commit 85f8271

Browse files
Merge pull request #38 from developmentseed/sortby
Add support for sortby
2 parents 3d90780 + bf12228 commit 85f8271

File tree

6 files changed

+94
-2
lines changed

6 files changed

+94
-2
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Add options to reduce the bandwidth required for returning record geometries.
55
- bbox-only=[bool] only return the bounding box in the return geometry
66
- geom-column=none don't return geometry as part of the return
77
- simplify=[float] Use ST_SnapToGrid(ST_Simplify(geom, [simplify]),[simplify]) to simplify and reduce precision of output geometry.
8+
- sortby=[+/-][field] support to sorting by a field
89

910
## 0.1.0
1011

tests/routes/test_items.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,52 @@ def test_output_response_type(app):
598598
assert response.headers["content-type"] == "application/ndjson"
599599
body = response.text.splitlines()
600600
assert len(body) == 10
601+
602+
603+
def test_items_sortby(app):
604+
"""Test /items endpoint with sortby options."""
605+
response = app.get("/collections/public.landsat_wrs/items?limit=1")
606+
assert response.status_code == 200
607+
assert response.headers["content-type"] == "application/geo+json"
608+
body = response.json()
609+
assert body["features"][0]["properties"]["ogc_fid"] == 1
610+
assert body["numberMatched"] == 16269
611+
612+
response = app.get("/collections/public.landsat_wrs/items?limit=1&sortby=ogc_fid")
613+
assert response.status_code == 200
614+
assert response.headers["content-type"] == "application/geo+json"
615+
body = response.json()
616+
assert body["features"][0]["properties"]["ogc_fid"] == 1
617+
assert body["numberMatched"] == 16269
618+
619+
response = app.get("/collections/public.landsat_wrs/items?limit=1&sortby=row")
620+
assert response.status_code == 200
621+
body = response.json()
622+
assert body["features"][0]["properties"]["row"] == 1
623+
assert body["numberMatched"] == 16269
624+
625+
response = app.get("/collections/public.landsat_wrs/items?limit=1&sortby=+row")
626+
assert response.status_code == 200
627+
body = response.json()
628+
assert body["features"][0]["properties"]["row"] == 1
629+
630+
response = app.get("/collections/public.landsat_wrs/items?limit=1&sortby=-row")
631+
assert response.status_code == 200
632+
body = response.json()
633+
assert body["features"][0]["properties"]["row"] == 248
634+
635+
response = app.get("/collections/public.landsat_wrs/items?limit=1&sortby=-row,path")
636+
assert response.status_code == 200
637+
body = response.json()
638+
assert body["features"][0]["properties"]["row"] == 248
639+
assert body["features"][0]["properties"]["path"] == 1
640+
641+
response = app.get("/collections/public.landsat_wrs/items?limit=1&sortby=path,-row")
642+
assert response.status_code == 200
643+
body = response.json()
644+
assert body["features"][0]["properties"]["row"] == 248
645+
assert body["features"][0]["properties"]["path"] == 1
646+
647+
# Invalid column name
648+
response = app.get("/collections/public.landsat_wrs/items?limit=1&sortby=something")
649+
assert response.status_code == 404

tifeatures/dbmodel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class Table(BaseModel):
5555
table: str
5656
dbschema: str = Field(..., alias="schema")
5757
description: Optional[str]
58-
id_column: Optional[str]
58+
id_column: str
5959
geometry_columns: List[GeometryColumn]
6060
properties: List[Column]
6161

@@ -94,7 +94,7 @@ def columns(self, properties: Optional[List[str]] = None) -> List[str]:
9494
"""Return table columns optionally filtered to only include columns from properties."""
9595
cols = [c.name for c in self.properties]
9696
if properties is not None:
97-
if self.id_column is not None and self.id_column not in properties:
97+
if self.id_column not in properties:
9898
properties.append(self.id_column)
9999

100100
geom_col = self.geometry_column()

tifeatures/dependencies.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,13 @@ def filter_query(
241241
return cql2_text_parser(query)
242242

243243
return None
244+
245+
246+
def sortby_query(
247+
sortby: Optional[str] = Query(
248+
None,
249+
description="Column Sort the items by Column (ascending (default) or descending).",
250+
)
251+
):
252+
"""Sortby dependency."""
253+
return sortby

tifeatures/factory.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
filter_query,
2121
ids_query,
2222
properties_query,
23+
sortby_query,
2324
)
2425
from tifeatures.errors import NotFound
2526
from tifeatures.layer import CollectionLayer
@@ -547,6 +548,7 @@ async def items(
547548
datetime_filter: Optional[List[str]] = Depends(datetime_query),
548549
properties: Optional[List[str]] = Depends(properties_query),
549550
cql_filter: Optional[AstType] = Depends(filter_query),
551+
sortby: Optional[str] = Depends(sortby_query),
550552
geom_column: Optional[str] = Query(
551553
None,
552554
description="Select geometry column.",
@@ -594,6 +596,7 @@ async def items(
594596
"offset",
595597
"bbox-only",
596598
"simplify",
599+
"sortby",
597600
]
598601
properties_filter = [
599602
(key, value)
@@ -608,6 +611,7 @@ async def items(
608611
datetime_filter=datetime_filter,
609612
properties_filter=properties_filter,
610613
cql_filter=cql_filter,
614+
sortby=sortby,
611615
properties=properties,
612616
limit=limit,
613617
offset=offset,

tifeatures/layer.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""tifeatures.layers."""
22

33
import abc
4+
import re
45
from dataclasses import dataclass
56
from typing import Any, ClassVar, Dict, List, Optional, Tuple
67

@@ -261,6 +262,29 @@ def _datetime_filter_to_sql(self, interval: List[str], dt_name: str):
261262
logic.V(dt_name) < logic.S(pg_funcs.cast(end, "timestamptz")),
262263
)
263264

265+
def _sortby(self, sortby: Optional[str]):
266+
sorts = []
267+
if sortby:
268+
for s in sortby.strip().split(","):
269+
parts = re.match(
270+
"^(?P<direction>[+-]?)(?P<column>.*)$", s
271+
).groupdict() # type:ignore
272+
273+
direction = parts["direction"]
274+
column = parts["column"].strip()
275+
if self.get_column(column):
276+
if direction == "-":
277+
sorts.append(logic.V(column).desc())
278+
else:
279+
sorts.append(logic.V(column))
280+
else:
281+
raise InvalidPropertyName(f"Property {column} does not exist.")
282+
283+
else:
284+
sorts.append(logic.V(self.id_column))
285+
286+
return clauses.OrderBy(*sorts)
287+
264288
def _features_query(
265289
self,
266290
*,
@@ -269,6 +293,7 @@ def _features_query(
269293
datetime_filter: Optional[List[str]] = None,
270294
properties_filter: Optional[List[Tuple[str, str]]] = None,
271295
cql_filter: Optional[AstType] = None,
296+
sortby: Optional[str] = None,
272297
properties: Optional[List[str]] = None,
273298
geom: str = None,
274299
dt: str = None,
@@ -288,6 +313,7 @@ def _features_query(
288313
geom=geom,
289314
dt=dt,
290315
)
316+
+ self._sortby(sortby)
291317
+ clauses.Limit(limit or 10)
292318
+ clauses.Offset(offset or 0)
293319
)
@@ -327,6 +353,7 @@ async def query(
327353
datetime_filter: Optional[List[str]] = None,
328354
properties_filter: Optional[List[Tuple[str, str]]] = None,
329355
cql_filter: Optional[AstType] = None,
356+
sortby: Optional[str] = None,
330357
properties: Optional[List[str]] = None,
331358
geom: str = None,
332359
dt: str = None,
@@ -370,6 +397,7 @@ async def query(
370397
datetime_filter=datetime_filter,
371398
properties_filter=properties_filter,
372399
cql_filter=cql_filter,
400+
sortby=sortby,
373401
properties=properties,
374402
geom=geom,
375403
dt=dt,

0 commit comments

Comments
 (0)