Resumen de la arquitectura
MapConductor actúa como puente entre la aplicación y varios SDK de mapas. La aplicación solo llama a la API de mapas unificada, y MapConductor envía el procesamiento al proveedor seleccionado. En Android (Kotlin + Compose), iOS (Swift + SwiftUI) y React (Web), la división de paquetes y la distribución interna de responsabilidades tienen el mismo diseño.
01 · Imagen general
El flujo del proceso es el mismo en cualquier plataforma: Aplicación → API de mapas unificada → Core → Controladores de SDK de mapas → SDK de mapas. Solo las 2 capas superiores son el código escrito por la aplicación; las 2 capas inferiores se pueden reemplazar.
IU y lógica de negocio
API de mapas unificada
Funciones básicas (mapa, marcadores, formas, eventos)
Controladores de proveedor
SDK de mapas (nativos / JavaScript)
Al cambiar de proveedor, solo se reemplazan los 2 niveles más bajos. Como el código de la interfaz está escrito para la API unificada, funciona tal cual.
02 · Configuración por plataforma
Core y los controladores están separados como paquetes. La aplicación solo añade Core y el controlador del proveedor que utiliza como dependencias.
android-sdk-core tiene el modelo común y la aplicación de diferencias, mientras que android-sdk-compose proporciona contenedores de estado para Compose. GeoJSON, agrupación y mapas de calor son independientes como módulos adicionales (android-geo-layer / android-marker-clustering / android-heatmap).
ios-sdk-core contiene el modelo común y la base de las vistas de SwiftUI, y cada vista de SwiftUI la proporcionan los paquetes del proveedor (ios-for-*). El controlador MapKit es para el SDK nativo de Apple y se puede usar sin cargas adicionales ni claves de API.
js-sdk-core es un núcleo TypeScript independiente de React con un modelo común y aplicación de diferencias (casi la misma estructura que el core para Kotlin / Swift). js-sdk-react lo porta a React y proporciona componentes como Marker / Polygon / Polyline / Circle / GroundImage / RasterLayer / InfoBubble. La vista de mapa en sí la exponen cada uno de los controladores (react-for-*) como un par “vista + hook”, por ejemplo MapLibreMapView / MapLibreMapView2D y useMapLibreViewState. GeoJSON, agrupación en clúster, mapas de calor e iconos son independientes como paquetes adicionales (react-geo-layer / react-marker-clustering / react-heatmap / react-icons).
03 · Composición de capas
Para que se puedan reutilizar en diferentes plataformas, las responsabilidades se dividen en 6 capas. Las capas superiores no conocen las implementaciones de las capas inferiores.
Gracias a esta separación, el código que se centra en “qué hacer con el mapa” en lugar de “qué plataforma” se puede desplegar tal cual en múltiples entornos. Las diferencias específicas de la plataforma se limitan, en principio, solo a los métodos de instalación / declaración de la capa multiplataforma.
En el caso de React Native
Web y React Native comparten el código que escribe la aplicación. Los objetos de estado y los componentes también son los mismos; solo debe cambiar el MapView que elija. La diferencia está debajo. En Web, el controlador invoca una biblioteca de mapas JavaScript en el mismo entorno de ejecución, mientras que en React Native pasa a través de un puente al SDK nativo de MapConductor y, finalmente, los SDK de mapas nativos realizan el renderizado.
React
React Native
La capa 3 en la tabla de arriba es el puente. En Web no interviene, ya que la interfaz de usuario y el renderizado se completan en el mismo entorno de ejecución. En React Native, como ambos se separan en diferentes entornos de ejecución, se necesita esta capa. Solo esta capa varía según la plataforma; por encima, no hay cambios.
Los proveedores que se pueden usar con React Native son Google Maps, MapLibre, ArcGIS y HERE, en total 4. Como se limitan a aquellos que tienen un módulo correspondiente en el lado nativo, son menos que los 13 proveedores de Web.
04 · Estructura interna de Core
Core está estructurado de manera que repite los mismos 7 roles para cada elemento, como marcadores, polilíneas, polígonos, círculos, imágenes de suelo y capas ráster. Si entiende uno, puede seguir los demás elementos de la misma forma.
Lo importante es la posición del límite. El lado Core (State / Entity / Manager / Controller / Overlay) está escrito en un código casi idéntico en los 3 idiomas, y para cada proveedor solo hay que escribir el Renderer y la declaración de que puede manejar esos elementos.
PolygonCapableInterface.kt
AbstractPolygonOverlayRenderer.kt
polygon/PolygonOverlayRenderer.swift
polygon/PolygonHoleSplit.swift
polygon/PolygonCapable.ts
AbstractPolygonOverlayRenderer.ts
05 · Flujo de aplicación de diferencias
La aplicación solo pasa una "matriz del estado que debería existir ahora". Core calcula la diferencia con la vez anterior, la divide en agregados, cambiados y eliminados y la pasa al controlador. Como no se recrean los objetos del mapa, el renderizado es estable incluso con una gran cantidad de elementos.
export interface OverlayRendererInterface<ActualType, StateType, EntityType> {
onAdd(data: StateType[]): Promise<Array<ActualType | null>> | Array<ActualType | null>;
onChange(data: Array<ChangeParamsInterface<EntityType>>): Promise<Array<ActualType | null>> | Array<ActualType | null>;
onRemove(data: EntityType[]): Promise<void> | void;
onPostProcess(): Promise<void> | void;
}onChange recibe tanto la Entity anterior (prev) como el State actual (current). El controlador puede actualizar solo las propiedades cambiadas y la firma es la misma en Kotlin / Swift.
06 · Cosas que el controlador debe implementar
El trabajo para soportar un nuevo SDK de mapas se limita a heredar del renderizador abstracto para cada elemento y rellenar las 3 operaciones. No se toca el modelo de Core ni la lógica de diferencias.
// El driver hereda del renderizador abstracto y solo implementa tres operaciones
internal class GoogleMapPolygonOverlayRenderer(
override val holder: GoogleMapViewHolder,
override val coroutine: CoroutineScope,
) : AbstractPolygonOverlayRenderer<GoogleMapActualPolygon>() {
override suspend fun createPolygon(state: PolygonState) = /* map.addPolygon(...) */
override suspend fun updatePolygonProperties(polygon, current, prev) = /* Aplica solo la diferencia */
override suspend fun removePolygon(entity: PolygonEntityInterface<GoogleMapActualPolygon>) =
entity.polygon.remove()
}Solo las 3 operaciones crear, actualizar y eliminar son específicas del proveedor. La determinación de diferencias, la gestión del registro y la prueba de impacto se llaman después de que Core ya las haya completado.
// La misma forma que en Kotlin: hereda del renderizador abstracto e implementa tres operaciones
final class MapKitPolygonOverlayRenderer: AbstractPolygonOverlayRenderer<MKPolygon> {
override func createPolygon(state: PolygonState) async -> MKPolygon? { /* MKPolygon(...) */ }
override func updatePolygonProperties(polygon, current, prev) async -> MKPolygon? { /* Aplica solo la diferencia */ }
override func removePolygon(entity: PolygonEntity<MKPolygon>) async { /* mapView.removeOverlay */ }
}
// El controlador usa la implementación genérica de Core tal cual
final class MapKitPolygonController: PolygonController<MKPolygon, MapKitPolygonOverlayRenderer> { }En Swift, "el controlador que tiene ese proveedor" se convierte en la lista de funciones. Las funciones que no tiene el lado del SDK, como agujeros en polígonos o animaciones de marcadores, se expresan no implementando el renderizador correspondiente o cambiando a una representación alternativa del lado Core (como PolygonHoleSplit, que divide en anillos simples sin agujeros).
// La misma forma que en Kotlin y Swift: hereda del renderizador abstracto e implementa tres operaciones
export class MapLibrePolygonOverlayRenderer extends AbstractPolygonOverlayRenderer<
MapLibreMapViewHolder,
MapLibreActualPolygon
> {
async createPolygon(state: PolygonState) { /* Crea el feature de GeoJSON */ }
async updatePolygonProperties({ current, prev }) { /* Aplica solo la diferencia */ }
async removePolygon(entity: PolygonEntity<MapLibreActualPolygon>) { /* Quítalo */ }
// Tras aplicar la diferencia, reescribe la fuente a partir de las entidades que quedan
override async onPostProcess() { this.layer.draw(this.polygonManager.allEntities()); }
}
// Declara lo que el driver admite implementando las interfaces Capable
export class MapLibreViewController extends BaseMapViewController
implements MapViewControllerInterface, MarkerCapable, PolygonCapable, /* … */ { }Incluso en TypeScript, solo create, update y delete son específicos del proveedor. En SDKs que dibujan capas de una sola vez como MapLibre, las diferencias en la forma de dibujar, como reescribir toda la Source en onPostProcess en lugar de eliminar elementos uno por uno, se encapsulan dentro del renderer. Los elementos que el driver puede manejar se representan mediante la interfaz Capable implementada por MapLibreViewController (por ejemplo, PolygonCapable), y si están disponibles se sabe por el tipo.
07 · Alcance de la abstracción y vías de escape
MapConductor no envuelve todas las funciones de cada SDK de mapas. Se enfoca en operaciones comúnmente utilizadas y, para requisitos que exceden esto, prepara dos vías de escape.
// map de la interfaz común es unknown: acótalo al tipo del proveedor antes de usarlo
const holder = mapViewState.getMapViewHolder();
const map = holder?.map as maplibregl.Map | undefined;
map?.addLayer({ id: 'buildings', type: 'fill-extrusion', source: 'composite' });Un diseño que mantiene la API común simple y, al mismo tiempo, no sacrifica las fortalezas específicas de cada proveedor. Para más detalles, consulte "Extensiones nativas".