Skip to content

GeoRectBounds

GeoRectBounds representa un área geográfica rectangular definida por dos esquinas: suroeste y noreste. Se utiliza para definir regiones de mapa, límites de imágenes de tipo ground image y para cálculos de viewport.

Constructor

class GeoRectBounds(
southWest: GeoPoint? = null,
northEast: GeoPoint? = null
)

Propiedades

Esquinas

  • southWest: GeoPoint?: esquina suroeste (inferior izquierda) del rectángulo.
  • northEast: GeoPoint?: esquina noreste (superior derecha) del rectángulo.
  • isEmpty: Boolean: devuelve true si los límites no están definidos.

Propiedades calculadas

  • center: GeoPoint?: punto central de los límites.
  • toSpan(): GeoPoint?: devuelve el “span” (ancho/alto) como un GeoPointInterface.

Pruebas con puntos y límites

Contiene un punto

fun contains(point: GeoPointInterface): Boolean
// Usage
val bounds = GeoRectBounds(
southWest = GeoPoint.fromLatLong(37.7649, -122.4294),
northEast = GeoPoint.fromLatLong(37.7849, -122.4094)
)
val testPoint = GeoPoint.fromLatLong(37.7749, -122.4194)
val isInside = bounds.contains(testPoint)
println("Point is inside bounds: $isInside")

Intersección de límites

fun intersects(other: GeoRectBounds): Boolean
// Usage
val bounds1 = GeoRectBounds(/* ... */)
val bounds2 = GeoRectBounds(/* ... */)
val doIntersect = bounds1.intersects(bounds2)
println("Bounds intersect: $doIntersect")

Ejemplos prácticos

Cálculo de límites de viewport

@Composable
fun ViewportBoundsExample() {
var viewportBounds by remember { mutableStateOf<GeoRectBounds?>(null) }
// Calcular el viewport a partir de los marcadores visibles
val markers = remember {
listOf(
GeoPoint.fromLatLong(37.7749, -122.4194),
GeoPoint.fromLatLong(37.7849, -122.4094),
GeoPoint.fromLatLong(37.7649, -122.4294),
GeoPoint.fromLatLong(37.7949, -122.3994)
)
}
LaunchedEffect(markers) {
val bounds = GeoRectBounds()
markers.forEach { marker ->
bounds.extend(marker)
}
viewportBounds = bounds
}
// Sustituye MapView por el proveedor de mapas que prefieras, como GoogleMapView o MapboxMapView
MapView(state = mapViewState) {
// Show all markers
markers.forEach { position ->
Marker(
position = position,
icon = ColorDefaultIcon(fillColor = Color.Blue)
)
}
// Show viewport bounds as a polygon
viewportBounds?.let { bounds ->
if (!bounds.isEmpty) {
val sw = bounds.southWest!!
val ne = bounds.northEast!!
val boundsPolygon = listOf(
sw,
GeoPoint.fromLatLong(sw.latitude, ne.longitude),
ne,
GeoPoint.fromLatLong(ne.latitude, sw.longitude),
sw // Close the polygon
)
Polygon(
points = boundsPolygon,
strokeColor = Color.Red,
strokeWidth = 2.dp,
fillColor = Color.Red.copy(alpha = 0.1f)
)
}
}
}
}

Carga basada en límites

@Composable
fun BoundsBasedLoadingExample() {
var currentBounds by remember { mutableStateOf<GeoRectBounds?>(null) }
var markersInBounds by remember { mutableStateOf<List<GeoPoint>>(emptyList()) }
// Simular todos los marcadores disponibles
val allMarkers = remember {
List(100) { i ->
GeoPoint.fromLatLong(
37.7 + (i % 10) * 0.01,
-122.5 + (i / 10) * 0.01
)
}
}
// Filter markers based on current bounds
LaunchedEffect(currentBounds) {
markersInBounds = currentBounds?.let { bounds ->
if (bounds.isEmpty) {
emptyList()
} else {
allMarkers.filter { marker ->
bounds.contains(marker)
}
}
} ?: emptyList()
}
Column {
Text("Markers in bounds: ${markersInBounds.size}/${allMarkers.size}")
Button(
onClick = {
// Simular el establecimiento de los límites del viewport
currentBounds = GeoRectBounds(
southWest = GeoPoint.fromLatLong(37.7, -122.5),
northEast = GeoPoint.fromLatLong(37.75, -122.45)
)
}
) {
Text("Set Viewport")
}
// Sustituye MapView por el proveedor de mapas que prefieras, como GoogleMapView o MapboxMapView
MapView(state = mapViewState) {
// Show only markers within bounds
markersInBounds.forEach { position ->
Marker(
position = position,
icon = ColorDefaultIcon(
fillColor = Color.Green,
scale = 0.8f
)
)
}
// Show current bounds
currentBounds?.let { bounds ->
if (!bounds.isEmpty) {
val sw = bounds.southWest!!
val ne = bounds.northEast!!
Polygon(
points = listOf(
sw,
GeoPoint.fromLatLong(sw.latitude, ne.longitude),
ne,
GeoPoint.fromLatLong(ne.latitude, sw.longitude),
sw
),
strokeColor = Color.Blue,
strokeWidth = 3.dp,
fillColor = Color.Blue.copy(alpha = 0.1f)
)
}
}
}
}
}

Editor interactivo de límites

@Composable
fun BoundsEditorExample() {
var bounds by remember {
mutableStateOf(
GeoRectBounds(
southWest = GeoPoint.fromLatLong(37.7649, -122.4294),
northEast = GeoPoint.fromLatLong(37.7849, -122.4094)
)
)
}
val onCornerDrag: (MarkerState) -> Unit = { markerState ->
val newPosition = markerState.position
when (markerState.extra) {
"SW" -> {
bounds = GeoRectBounds(
southWest = newPosition as GeoPoint,
northEast = bounds.northEast
)
}
"NE" -> {
bounds = GeoRectBounds(
southWest = bounds.southWest,
northEast = newPosition as GeoPoint
)
}
}
}
// Sustituye MapView por el proveedor de mapas que prefieras, como GoogleMapView o MapboxMapView
MapView(
state = mapViewState
) {
// Bounds visualization
if (!bounds.isEmpty) {
val sw = bounds.southWest!!
val ne = bounds.northEast!!
// Bounds rectangle
Polygon(
points = listOf(
sw,
GeoPoint.fromLatLong(sw.latitude, ne.longitude),
ne,
GeoPoint.fromLatLong(ne.latitude, sw.longitude),
sw
),
strokeColor = Color.Red,
fillColor = Color.Red.copy(alpha = 0.2f)
)
// Corner markers
Marker(
position = sw,
icon = ColorDefaultIcon(
fillColor = Color.Green,
label = "SW"
),
draggable = true,
extra = "SW",
onDrag = onCornerDrag
)
Marker(
position = ne,
icon = ColorDefaultIcon(
fillColor = Color.Red,
label = "NE"
),
draggable = true,
extra = "NE",
onDrag = onCornerDrag
)
}
}
}

Manejo especial

Línea internacional de cambio de fecha

GeoRectBounds maneja rectángulos que cruzan la línea internacional de cambio de fecha (longitud ±180°):

// Bounds crossing the date line
val pacificBounds = GeoRectBounds(
southWest = GeoPoint.fromLatLong(20, 170), // West of date line
northEast = GeoPoint.fromLatLong(40, -170) // East of date line
)
val pointInPacific = GeoPoint.fromLatLong(30, 175)
val containsPoint = pacificBounds.contains(pointInPacific) // true

Manejo de límites vacíos

val emptyBounds = GeoRectBounds()
println(emptyBounds.isEmpty) // true
println(emptyBounds.center) // null
println(emptyBounds.toSpan()) // null
// Extending empty bounds
emptyBounds.extend(GeoPoint.fromLatLong(37.7749, -122.4194))
println(emptyBounds.isEmpty) // false

Métodos utilitarios

Representación de cadena

// For debugging
val bounds = GeoRectBounds(
southWest = GeoPoint.fromLatLong(37.7649, -122.4294),
northEast = GeoPoint.fromLatLong(37.7849, -122.4094)
)
println(bounds.toString())
// Output: ((37.7649, -122.4294), (37.7849, -122.4094))

Valor de URL

fun toUrlValue(precision: Int = 6): String
// Usage for API calls
val urlString = bounds.toUrlValue() // "37.764900,-122.429400,37.784900,-122.409400"
val preciseString = bounds.toUrlValue(precision = 8)

Comparación de igualdad

fun equalsTo(other: GeoRectBounds): Boolean
// Uso
val bounds1 = GeoRectBounds(/* ... */)
val bounds2 = GeoRectBounds(/* ... */)
val areEqual = bounds1.equalsTo(bounds2)

Ajustar la cámara a los límites

Para mover y hacer zoom de la cámara de modo que un GeoRectBounds sea completamente visible, usa fitBounds:

val bounds = GeoRectBounds(
southWest = GeoPoint.fromLatLong(37.7649, -122.4294),
northEast = GeoPoint.fromLatLong(37.7849, -122.4094)
)
mapViewState.fitBounds(bounds, padding = 32)

El parámetro padding agrega un margen en píxeles en todos los lados del espacio de pantalla. Cada proveedor calcula el nivel de zoom resultante de forma nativa, por lo que el zoom exacto puede diferir ligeramente entre proveedores.

Un patrón común es construir límites desde un conjunto de puntos y luego ajustar:

val bounds = GeoRectBounds()
markers.forEach { bounds.extend(it.position) }
if (!bounds.isEmpty) {
mapViewState.fitBounds(bounds, padding = 48)
}