Documentación / Capas de extensión / Crear un módulo de extensión

Crear un módulo de extensión

El mapa de calor, la capa GeoJSON y la agrupación de marcadores son todos paquetes separados que se agregan desde fuera sin modificar Core. Dado que la misma interfaz es pública, terceros también pueden distribuir sus propias extensiones de la misma manera. Esta página muestra los pasos.

ANDROID
com.mapconductor:core
iOS
MapConductorCore
REACT
@mapconductor/js-sdk-core

00 · Forma de un módulo de extensión

Un módulo de extensión es un paquete independiente que depende solo del módulo principal. No depende de los paquetes del proveedor (react-for-*, android-for-*, ios-for-*). Si se cumple esto, su extensión funcionará sin importar qué proveedor de mapas elija el usuario.

OK

Usar solo los tipos públicos del núcleo (estado, recopilador, contrato de controlador, TileServer, ServiceRegistry).

NG

Importar el paquete del proveedor. Convertir el controlador a otro tipo y revisar los campos internos.

Los tres ya enviados sirven como implementaciones de referencia. El camino más rápido es leer el que más se parezca a lo que quieres crear.

Implementación de referencia
heatmap            dibuja mosaicos ráster por su cuenta · servidor de mosaicos + RasterLayer
geojson-layer      convierte vectores masivos en mosaicos · servidor de mosaicos + RasterLayer
marker-clustering  reelabora marcadores y los devuelve   · MarkerState + registro de servicios
Plataforma

01 · Crear un paquete

Las dependencias se limitan solo al núcleo. El proveedor es lo que elige la aplicación del usuario, por lo que no es visible desde tu paquete.

build.gradle.kts
dependencies {
    implementation("com.mapconductor:core")
    implementation("com.mapconductor:compose") // Si expones un Composable
    // No dependas de android-for-*
}

02 · Definir el estado

El usuario interactúa con el objeto de estado. Si sigues la misma convención que los tipos de estado del núcleo (como `MarkerState`), el usuario no tendrá que aprender cosas adicionales. Si incluyes un mecanismo para notificar cambios de valor (huella digital), puedes ponerlo en el colector del núcleo para recibir actualizaciones incrementales.

Elementos proporcionados por el núcleo
OverlayCollector       un mapa id → estado; reúne alta/baja/diferencia y avisos de cambio
ComponentState         el contrato de un estado que lleva un id (Android)
fingerPrint()          un valor de comparación con solo lo que afecta al dibujo

03 · Elegir la salida de renderizado

Esta es la decisión de diseño más importante. En lugar de escribir código de renderizado para cada proveedor, lo envías a las salidas que ya tiene el núcleo. Hay dos salidas.

A · RasterLayer

Dibujar tiles uno mismo

Registrar una función de renderizado en un TileServer local y colocar una sola capa RasterLayer que apunte a esa plantilla de URL. Los mapas de calor y las capas de GeoJSON funcionan así. Cuantos más elementos, más ventajoso; la apariencia coincide completamente con cualquier proveedor.

B · Superposición existente

Devolver como marcadores o formas

Procesar la entrada y convertirla a MarkerState / PolygonState, etc., y escribirla en el recopilador. A partir de ahí, la ruta de renderizado normal del proveedor se encarga. El agrupamiento funciona así. Los clics y el arrastre funcionan tal cual.

A · Servidor de mosaicos + RasterLayer (igual que react-heatmap)
val tileServer = TileServerRegistry.get()
val renderer = MyTileRenderer(tileSize = 256)   // Contiene la función que dibuja un solo mosaico

DisposableEffect(groupId) {
    tileServer.register(groupId, renderer)
    onDispose { tileServer.unregister(groupId) }
}

val layer = remember {
    RasterLayerState(
        id = "my-ext-$groupId",
        source = RasterLayerSource.UrlTemplate(
            template = tileServer.urlTemplate(groupId, renderer.tileSize),
            tileSize = renderer.tileSize,
            scheme = TileScheme.XYZ,
        ),
    )
}
RasterLayer(layer)
Si eliges B, en React no escribes directamente en el colector, sino que obtienes el `MarkerRenderingSupport` registrado por el proveedor desde el registro de servicios y creas un renderizador (ver 05). Esto es para permitir que el proveedor pueda reemplazar la ruta de renderizado.

04 · Cámara y limpieza

Si desea seguir el zoom o el desplazamiento, no manipule directamente los eventos del controlador del mapa. Esto daría lugar a una competencia por los oyentes de ranura única y se rompería en entornos donde se cargan dos extensiones. Si se registra como controlador de superposición, los cambios de cámara llegarán por la misma ruta que otras superposiciones.

El mismo formato que `HeatmapCameraController.kt`
class MyCameraController(
    private val renderer: MyTileRenderer,
) : OverlayControllerInterface<Unit, Unit>, OnCameraChangeReceiverInterface {
    override val zIndex: Int = 0
    override suspend fun add(data: List<Unit>) {}
    override suspend fun update(state: Unit) {}
    override suspend fun clear() {}
    override fun find(position: GeoPointInterface): Unit? = null

    override suspend fun onCameraChanged(mapCameraPosition: MapCameraPosition) {
        renderer.updateCameraZoom(mapCameraPosition.zoom)
    }

    override fun destroy() {}
}

// Regístralo
val mapController = LocalMapViewController.current
DisposableEffect(mapController, cameraController) {
    mapController.registerOverlayController(cameraController)
    onDispose { cameraController.destroy() }
}
Debe asegurarse de cancelar todo lo registrado. Dado que hay usuarios que solo cambian el proveedor sin descartar el mapa, una cancelación olvidada mantendría el renderizador del proveedor anterior retenido.

05 · Transferir funcionalidad

Solo se usa si los 4 anteriores no son suficientes. Cuando necesita algo que solo el proveedor puede crear, lo recibe a través de `MapServiceRegistry`. Dado que el registro y la obtención se realizan con claves tipadas, no se requiere conversión.

Un ejemplo concreto es la agrupación de marcadores. Dado que el tipo de marcador nativo difiere según el proveedor, la agrupación no puede crear el renderizador por sí misma. Por lo tanto, se recupera el `MarkerRenderingSupport` registrado por el proveedor y se crea un renderizador desde allí.

El lado de la resolución (módulo de extensión)
// Define la clave como un object singleton
object MyCapabilityKey : MapServiceKey<MyCapability>

// Recupérala. Si no está registrada es null, así que decide si sigues sin la función o te detienes
val services = LocalMapServiceRegistry.current
val capability = services.get(MyCapabilityKey) ?: return
El lado del registro (proveedor)
// Un registro por mapa, en poder del estado (igual que en react e ios)
state.serviceRegistry.put(MyCapabilityKey, myCapability)

// Cuando desaparezca usa remove(), no clear(), para no arrastrar otras capacidades
DisposableEffect(state) {
    onDispose { state.serviceRegistry.remove(MyCapabilityKey) }
}
La clave debe ser expuesta obligatoriamente por el lado de la resolución (módulo de extensión). El proveedor importa esa clave y la registra. Si se hace al revés, el proveedor dependerá de la extensión, invirtiendo la dirección de la dependencia.

Que se debe cumplir

Si cumple estos 4 puntos, no se romperá cuando los usuarios cambien el proveedor o lo usen junto con otra extensión.

1 · No importar el paquete del proveedor

Si es necesario, debería ser una función que se reciba a través de la ServiceRegistry.

2 · No convertir el controlador para revisar su interior

react-heatmap hacía esto anteriormente y guardaba/restauraba un único slot de escucha de cámara. Cuando se cargan dos extensiones, se sobrescriben entre sí. Ya se reemplazó por registerOverlayController público.

3 · Deshacer siempre lo registrado

Controladores de superposición, grupos de TileServer, claves de ServiceRegistry. remove() solo quita un elemento.

4 · Si no está registrado, desactivar silenciosamente

En proveedores sin la capacidad necesaria, no debe dibujarse nada sin lanzar una excepción. Los usuarios pueden seguir usando otras partes.

Páginas relacionadas