-
Notifications
You must be signed in to change notification settings - Fork 360
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
MapKeyTransform method aliases #2334
Conversation
- I had hoped to add more methods like this, so that users would never need to use MapKeyTransform directly. Unfortunately, due to `LayoutDefinition` and `SpatialKey` living in the `geotrellis-spark` package, attempting that would result in some weird circular dependencies. We would benefit from a central `geotrellis-types` or `geotrellis-core` package.
def apply(p: Point): SpatialKey = apply(p.x, p.y) | ||
|
||
/** Fetch the [[SpatialKey]] that corresponds to some coordinates in some CRS on the Earth. */ | ||
def pointCoordsToKey(x: Double, y: Double): SpatialKey = apply(x, y) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like a place where overloading is helpful to overload between pointToKey(Point(3,5))
and pointToKey(x=3,y=5)
are both unsurprising and more discoverable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This I've added.
def apply[K: SpatialComponent](key: K): Extent = { | ||
apply(key.getComponent[SpatialKey]) | ||
} | ||
def keyLikeToExtent[K: SpatialComponent](key: K): Extent = apply(key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this could actually take care of the SpatialKey case just fine. Do you think its more clear to have an explicit function not based on SpatialComponent
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think SpatialKey => Extent
signals its intent better. If the user knows what the purpose of SpatialComponent
is, then should be able to make the getComponent
call themselves (if say their key type was SpaceTimeKey
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a buy that, what are some of the impacted lines in our codebase for reference ?
def apply(key: SpatialKey): Extent = | ||
apply(key.col, key.row) | ||
/** 'col' and 'row' correspond to a [[SpatialKey]] column and row in some grid. */ | ||
def coordsToExtent(col: Int, row: Int): Extent = apply(col, row) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be keyToExtent
overload? coords
is a little overloaded meaning, is it pixel cords, tile cords, map cords, a bit of ambiguity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think those are the same thing. See the apply
that it calls out to right below it.
import org.apache.spark.rdd.RDD | ||
|
||
/** A SpatialKey designates the spatial positioning of a layer's tile. */ | ||
case class SpatialKey(col: Int, row: Int) extends Product2[Int, Int] { | ||
def _1 = col | ||
def _2 = row | ||
|
||
/** Retrieve the [[Extent]] that corresponds to this key, given a layout. */ | ||
def tileExtent(layout: LayoutDefinition): Extent = layout.mapTransform(this) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reference to tile
here makes this key, which is agnostic of Value type, seem like it's specific to Tile
. We've been avoiding placing specific language on things that could be generally useful, e.g. to point clouds that are stored based on spatial keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, thats tough. SpatialKey
covers a rectangular, tile-like area on the map based on LayoutDefinition
.
def apply[K: SpatialComponent](key: K): Extent = apply(key.getComponent[SpatialKey]) | ||
|
||
/** Get the [[Extent]] corresponding to a [[SpatialKey]] in some zoom level. */ | ||
def tileExtent(key: SpatialKey): Extent = apply(key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, I think we should keep away from naming things with tile
.
Suggestion: keyExtent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was previously toExtent
. Which do you think is better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a talk with @echeipesh , the current vote is SpatialKey.extent
(maximum minimalism, the word "key" doesn't need to be repeated since that's implied by the parent class), and keyToExtent
for the MapKeyTransform
methods so that they match the naming of their sibling methods. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
- And the underlying methods in `MapKeyTransform` have been renamed `keyToExtent` to match the naming scheme of the other methods there.
TODO
.apply
variantMotivation
MapKeyTransform
is a "worker class", it does not itself represent data. It provides mechanisms for transformations involvingSpatialKey
s, namely:Extent
that corresponds to some keyGridBounds
) that corresponds to some Geometry envelope (anExtent
)Until now, each of these transformations occurred through a very overloaded
.apply
method, makingMapKeyTransform
somewhat of a mysterious black box. This PR adds sensibly named aliases for each of these transformations, likekeyToExtent: SpatialKey => Extent
.Further Work
Duplicate Deprecations
If it's true that reducing API surface area increases discoverability and improves usability, is it necessary to have:
apply: Point => SpatialKey
andapply: (Double, Double) => SpatialKey
apply: (Int, Int) => Extent
andapply: SpatialKey => Extent
andapply[K: SpatialComponent]: K => Extent
given that the extra forms are simple pure transformations that could occur in application code?
For instance, as currently exists in the code base:
My recommendation is to keep
SpatialKey => Extent
andPoint => SpatialKey
and deprecate the duplicates..apply
DeprecationsEchoing the "surface area" argument, would it be prudent to deprecate the
.apply
methods as well, given that they were the original source of confusion? Replacing their usage with the named aliases within GT couldn't be more than a 15 minute find-replace job.geotrellis-core
Each of these transformations can be modelled by the form
A.someAToB: LayoutDefinition => B
, where the data of theA
is used implicitly inside the method. Here, the transformation is a method onA
, not a function throughMapKeyTransform
. For the case ofSpatialKey
andExtent
, it's:SpatialKey.toExtent: LayoutDefinition => Extent
. This PR provides this method as well, so that users don't need to go through aMapKeyTransform
.Unfortunately, due to the way the project is structured, we can't provide type-local methods for the other types without incurring circular dependencies:
Point.toKey: LayoutDefinition => SpatialKey
LayoutDefinition
lives ingeotrellis.spark.tiling
SpatialKey
lives ingeotrellis.spark
Point
lives ingeotrellis.vector
GridBounds.toExtent: LayoutDefinition => Extent
GridBounds
lives ingeotrellis.raster
Extent
lives ingeotrellis.vector
and so on. So
MapKeyTransform
who lives ingeotrellis.spark.tiling
has to act as a sort of"type broker", a place for all these necessary transformations to live. This (and other issues) would be solved by a central
geotrellis-core
package, which would contain types useful to any conceivable GeoTrellis application (i.e. one shouldn't need to depend onspark
just to get access toSpatialKey
). This way, we could get rid ofMapKeyTransform
, and each transformation could occur directly on the class in question.