Skip to content

WIP connect titiler for custom search tiling #97

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

Closed
wants to merge 2 commits into from

Conversation

kylebarron
Copy link
Contributor

@kylebarron kylebarron commented Feb 24, 2021

This is a draft PR to connect titiler with STAC api's search endpoint.

Notes:

  • This only cares about the tile endpoint of the MosaicTilerFactory; I haven't tested anything else.
  • This adds an extension file, but doesn't add it to the main app by default because that would require adding more dependencies: cogeo_mosaic, morecantile, rio_tiler. (Though I guess the STAC API mounts titiler by default already, so I guess this isn't actually adding any more dependencies?)

Comment on lines +173 to +179
ids: Optional[str] = Query(None)
datetime: Optional[str] = Query(None)
limit: Optional[int] = Query(10)
query: Optional[str] = Query(None)
token: Optional[str] = Query(None)
fields: Optional[str] = Query(None)
sortby: Optional[str] = Query(None)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: add title and description for each of these, so it shows up in openapi

Custom extension for sending query results to a titiler DynamicSTAC endpoint
"""

client: CoreCrudClient = attr.ib(default=attr.Factory(CoreCrudClient))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses the base CoreCrudClient as all it needs is the client.get_search method. I figured it wasn't necessary to subclass CoreCrudClient like the TilesClient does

https://github.com/arturo-ai/arturo-stac-api/blob/7ca0c8790ac64bbd359e88933ef94f25ece5b383/stac_api/clients/tiles/ogc.py#L17-L41

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. TilesClient arguably shouldn't subclass CoreCrudClient either. I think composition is a better pattern here.

"""
dynamic_stac_endpoint = MosaicTilerFactory(
reader=DynamicStacBackend,
reader_options={"client": self.client},
Copy link
Contributor Author

@kylebarron kylebarron Feb 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the best way to pass the client to the MosaicTilerFactory? I need to access the client in the MosaicBackend.get_assets method, but it seems like it shouldn't be on the reader_options. Maybe it's best to subclass the DynamicStacBackend within this extension so you can attach the client there...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it seems like it shouldn't be on the reader_options.

I think it's ok to use the reader_options here and just pass a backend_options dict within.

reader_options={"backend_options":{"client": self.client}},

that's how we do for STAC https://github.com/developmentseed/cogeo-mosaic/blob/master/cogeo_mosaic/backends/stac.py#L76

I agree it's not perfect

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, in fact you are already creating a custom Mosaic Backend so you can still use reader_options={"client": self.client} and make client a required parameter for your DynamicStacBackend backend


print("feature_collection")
print(feature_collection)
return [default_stac_accessor(f) for f in feature_collection["features"]]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand this... seems like here I'm returning the id of each stac item, rather than the stac items themselves... Seems like it would be better to return feature_collection['features'] and then use the resulting STAC dicts directly.



@attr.s
class FakeRequest:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

@geospatial-jeff geospatial-jeff Feb 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A way to get around this is to inject a dependency which itself returns a request object. This works because dependencies always have access to the request:

from starlette.requests import Request

def request_dependency(request: Request):
    return request

Comment on lines +230 to +234
router_prefix="test-dynamic-search",
)

app.include_router(
dynamic_stac_endpoint.router, prefix="/dynamic-stac", tags=["Dynamic STAC"]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems unnecessary to have both of these router prefixes, and it looks like /dynamic-stac overrides test-dynamic-search anyways. Also, open to other prefix names.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is unnecessary, I think i'd rather include the prefix on the call to include_router.

@geospatial-jeff
Copy link
Collaborator

geospatial-jeff commented Feb 24, 2021

To summarize our slack convo:

The "all-in-one" approach where we overload a single endpoint to POST /search and return tiles is simpler to implement but doesn't align that well with the way that others typically implement STAC + tiling. The typical approach is to add a new link which references a tile layer for that particular resource.

{
  "title": "tile-layer",
  "type": "image/jpeg",
  "href": "http://tile-layer/{z}/{x}/{y}.jpg",
  "templated": true,
  "rel": "tiles",
}

In my mind this has two primary benefits:

  • Its more canonical "STAC", where additional info/metadata/functionality etc. is exposed as a link on the particular resource.
  • It lets us hook into specifications like OGC API Tiles which has a very sensible and small core that provides nice interoperable way of linking STAC to tiles. This is actually how the existing titiler extensions works, albeit only on a single item so its a much easier problem.

My dream for STAC + tiling is exposing tiles links at the Item, ItemCollection, and Catalog levels; where item collection and catalog links are mosaicing the appropriate items. The actual implementation of this is difficult though (imo order of increasing difficulty is Item -> ItemCollection -> Catalog). And the "all-in-one" approach provides a much more constrained example that I think we should move forward with because it should give us valuable information about how to implement STAC + tiling as described above.

In particular I think breaking up this repo into appropriate parts (stac-api / db / extension - ref @vincentsarago) will clear up separation of concerns and make thinking through the implementation a little easier.

@kylebarron
Copy link
Contributor Author

In general I think it makes sense to put tiling rel links on Item, ItemCollection, and Catalog, however it seems you hit unnecessary back-and-forth HTTP requests if the client needs to first create an ItemCollection via the /search endpoint, then post those stac items back to tile them.

exposing tiles links at the Item, ItemCollection, and Catalog levels; where item collection and catalog links are mosaicing the appropriate items

I think this makes sense, but in my mind this PR is essentially intending to serve the "tile a Catalog" problem. For almost any Catalog, like Landsat 8/Sentinel 2, you have way too many images per relevant tile, so you still need a search within the catalog.

@kylebarron
Copy link
Contributor Author

The typical approach is to add a new link which references a tile layer for that particular resource.

{
  "title": "tile-layer",
  "type": "image/jpeg",
  "href": "http://tile-layer/{z}/{x}/{y}.jpg",
  "templated": true,
  "rel": "tiles",
}

By the way this isn't necessarily in conflict with a /search/tiles approach. A /search endpoint that returns an ItemCollection could have a tiles link back to /search/tiles in order to tile the contained features. And it could set /search params like limit and token so that those exact same features would be used. In effect it would mostly trade another SELECT against the db for avoiding POSTing the ItemCollection back to a tiler.

@geospatial-jeff
Copy link
Collaborator

geospatial-jeff commented Feb 24, 2021

I think this makes sense, but in my mind this PR is essentially intending to serve the "tile a Catalog" problem.

Makes sense to me. Catalog tiling should happen through /search?collections=collection_name to minimize search space. Catalog tiling is quite similar to ItemCollection tiling in this way, at the end of the day it all comes down to creating a mosaic from List[Item].

By the way this isn't necessarily in conflict with a /search/tiles approach. A /search endpoint that returns an ItemCollection could have a tiles link back to /search/tiles in order to tile the contained features.

This is a great approach to make this more consumable, let's roll with it.

@vincentsarago
Copy link
Member

FYI, I've started something similar over https://github.com/stac-utils/titiler-pgstac

Note: This is not an extension and doesn't add new endpoint to a stac-api but create a new standalone app

@lossyrob
Copy link
Member

What's the status of this PR? IMO stac-fastapi shouldn't host its own tiler - the titiler-pgstac is a good example of standing up a tiler that uses STAC but a separate service that can be deployed independently. In our implementation of stac-fastapi, we override the pgstac backend core client in order to iterate over collections/items and inject tiler links where appropriate. I think this approach allows stac-fastapi to concentrate on just the STAC API endpoints, but allows customization by users if they want to add titiler or other tiling capability. I could see work done to generalize and implement a link injection mechanism so that other libraries can write hooks into the STAC objects returned by stac-fastapi so that the link/asset injection logic can live in that external project. But I don't think we should create tilers directly in stac-fastapi.

@geospatial-jeff
Copy link
Collaborator

I agree the code for such an integration should live somewhere else. Closing this PR.

I could see work done to generalize and implement a link injection mechanism so that other libraries can write hooks into the STAC objects returned by stac-fastapi so that the link/asset injection logic can live in that external project.

This would be a good feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants