Layer Model
SpatialKey
refers to a tile in a LayoutDefinition
, a regular grid of tiles covering a geographic area:
Note that the spatial keys are indexed from upper left while map bounding box is indexed from lower left. The spatial key convention is consistent with pixel column and row indexes of a raster while the map convention is conistent with cartisian (X,Y) plane. SpatialKey
instances has a map position only through corresponding LayoutDefinition
.
import geotrellis.layer.{ LayoutDefinition, SpatialKey }
import geotrellis.raster.GridExtent
import geotrellis.vector.{ Extent, Point, LineString }
val layout = LayoutDefinition(
grid = GridExtent(Extent(0, 0, 100, 100), cols = 100, rows = 100),
tileCols = 10, tileRows = 10
)
// layout: LayoutDefinition = LayoutDefinition(
// Extent(0.0, 0.0, 100.0, 100.0),
// TileLayout(10, 10, 10, 10)
// )
// lower left, upper left, upper right, lower right:
SpatialKey(0, 9).extent(layout)
// res0: Extent = Extent(0.0, 0.0, 10.0, 10.0)
SpatialKey(0, 0).extent(layout)
// res1: Extent = Extent(0.0, 90.0, 10.0, 100.0)
SpatialKey(9, 0).extent(layout)
// res2: Extent = Extent(90.0, 90.0, 100.0, 100.0)
SpatialKey(9, 9).extent(layout)
// res3: Extent = Extent(90.0, 0.0, 100.0, 10.0)
MapKeyTransform
instance on LayoutDefinition
has various utility methods to transition from tile addressing to map addressing:
layout.mapTransform.pointToKey(Point(5, 5))
// res4: SpatialKey = SpatialKey(0, 9)
layout.mapTransform.pointToKey(Point(15, 15))
// res5: SpatialKey = SpatialKey(1, 8)
layout.mapTransform.keyToExtent(SpatialKey(1,8))
// res6: Extent = Extent(10.0, 10.0, 20.0, 20.0)
layout.mapTransform.extentToBounds(Extent(5, 5, 15, 15))
// res7: geotrellis.layer.package.TileBounds = GridBounds(0, 8, 1, 9)
layout.mapTransform.keysForGeometry(LineString(Point(5,5), Point(25,5)))
// res8: Set[SpatialKey] = Set(
// SpatialKey(2, 9),
// SpatialKey(1, 9),
// SpatialKey(0, 9)
// )
Webmaps use a tile pyramid where each zoom level produce a power of two reduction in map pixel count. You can use ZoomedLayoutScheme
to generate layout definitions.
import geotrellis.layer.ZoomedLayoutScheme
import geotrellis.proj4.WebMercator
val scheme = ZoomedLayoutScheme(WebMercator, tileSize = 256)
// scheme: ZoomedLayoutScheme = geotrellis.layer.ZoomedLayoutScheme@75856571
val zoom7 = scheme.levelForZoom(7).layout
// zoom7: LayoutDefinition = LayoutDefinition(
// Extent(
// -2.0037508342789244E7,
// -2.0037508342789244E7,
// 2.0037508342789244E7,
// 2.0037508342789244E7
// ),
// TileLayout(128, 128, 256, 256)
// )
val zoom8 = scheme.levelForZoom(8).layout
// zoom8: LayoutDefinition = LayoutDefinition(
// Extent(
// -2.0037508342789244E7,
// -2.0037508342789244E7,
// 2.0037508342789244E7,
// 2.0037508342789244E7
// ),
// TileLayout(256, 256, 256, 256)
// )
val ottawaWM = Extent(-8621691, 5604373, -8336168, 5805297)
// ottawaWM: Extent = Extent(-8621691.0, 5604373.0, -8336168.0, 5805297.0)
val tileBounds = zoom7.mapTransform.extentToBounds(ottawaWM)
// tileBounds: geotrellis.layer.package.TileBounds = GridBounds(36, 45, 37, 46)
val tileExtent = zoom7.mapTransform.boundsToExtent(tileBounds)
// tileExtent: Extent = Extent(
// -8766409.899970295,
// 5322463.153553393,
// -8140237.7642581295,
// 5948635.289265556
// )
// Note that this is snapping out extent to closest tile borders
tileExtent.contains(ottawaWM)
// res9: Boolean = true
ottawaWM.contains(tileExtent)
// res10: Boolean = false
// These X/Y keys will cover Ottawa on a slippy map at zoom 7
zoom8.mapTransform.keysForGeometry(ottawaWM.toPolygon())
// res11: Set[SpatialKey] = Set(
// SpatialKey(74, 91),
// SpatialKey(72, 92),
// SpatialKey(72, 90),
// SpatialKey(74, 92),
// SpatialKey(73, 91),
// SpatialKey(73, 90),
// SpatialKey(74, 90),
// SpatialKey(72, 91),
// SpatialKey(73, 92)
// )