← GeoStream

13 — Mapas y proveedores

ID: GS-MAP-001 · Versión: 0.2.0 · Fase: F9 · Decisión: ADR-0013

1. Principio

La ubicación no es el mapa. GeoStream distribuye coordenadas; mostrarlas, geocodificarlas o calcular rutas son responsabilidades separadas, con licencias, costos y cuotas distintos. El núcleo (geostream) funciona sin ningún proveedor (FR-SDK-016, NFR-RES-001).

Una "pila de mapa" tiene piezas independientes:

Pieza Qué es Ejemplos
Renderer SDK que dibuja el mapa en la app MapLibre (maplibre_gl), Google Maps (google_maps_flutter)
Datos/teselas y estilo Las teselas vectoriales o ráster y su estilo Teselas propias (OpenMapTiles, PMTiles), MapTiler, Stadia, Google
Geocodificación Texto ↔ coordenadas Google Geocoding, Pelias, Nominatim propio
Rutas Cálculo de rutas/ETA Google Routes, Valhalla, OSRM
Map-matching Ajustar trayectorias a la red vial Valhalla Meili (post-GA, servidor, F13)

2. Restricciones de licencia que condicionan el diseño

3. Interfaces (paquete geostream_maps, normativas)

enum ProviderId { google, maplibre, mapbox, here, selfHosted, osmCommunity }

abstract interface class MapRenderer {
  ProviderId get provider;
  /// Widget de mapa que pinta los miembros de una SessionView.
  Widget buildMap({
    required Stream<SessionView> sessionView,
    MemberMarkerStyle Function(MemberView member)? markerStyle,
    CameraPolicy camera = const CameraPolicy.fitMembers(),
    bool interpolate = true,
    bool showAccuracyCircle = true,
    String? ownMemberId,
  });
}

abstract interface class GeocodingProvider {
  ProviderId get provider;
  Future<List<GeocodeResult>> search(String query, {GeoBounds? bias, String? language});
  Future<List<GeocodeResult>> reverse(double latitude, double longitude, {String? language});
}

abstract interface class RoutingProvider {
  ProviderId get provider;
  Future<RouteResult> route(List<Waypoint> waypoints, {TravelMode mode = TravelMode.driving});
}

final class ProviderStack {
  /// Lanza ConfigurationException si la combinación está prohibida (§4).
  factory ProviderStack({
    required MapRenderer renderer,
    GeocodingProvider? geocoding,
    RoutingProvider? routing,
    bool allowCommunityServers = false, // solo desarrollo
  });
  MapRenderer get renderer;
  GeocodingProvider? get geocoding;
  RoutingProvider? get routing;
}

Reglas:

4. Reglas de compatibilidad de ProviderStack

Regla Motivo
Si geocoding.provider == google o routing.provider == google → renderer.provider DEBE ser google Términos de Google (§2)
osmCommunity en cualquier pieza → solo si allowCommunityServers == true; en builds de release se registra advertencia Políticas de uso de OSM
mapbox, here → combinaciones a definir al crear su adaptador, tras revisar sus términos Pendiente

Matriz de proveedores (consulta inicial; revalidar en F9):

Proveedor Pieza Licencia/términos Costo Atribución Observaciones
MapLibre GL Renderer Código abierto (verificar licencia exacta del paquete Flutter en F9) Gratis Según teselas Requiere una fuente de teselas y un estilo
Teselas propias (OpenMapTiles / PMTiles) Datos ODbL (datos OSM) + licencia del esquema Infraestructura propia "© OpenStreetMap contributors" Mejor opción de privacidad
MapTiler / Stadia / similares Datos Comercial Por uso Proveedor + OSM Clave de cliente restringida por app
Google Maps Renderer + datos + geocoding + rutas Términos de Google Maps Platform Por uso (verificar precios vigentes) Google No mezclar con mapas no-Google
Pelias / Nominatim propio Geocoding Código abierto Infraestructura propia OSM Nominatim público no apto para producción
Valhalla / OSRM Rutas Código abierto Infraestructura propia OSM Post-GA para servidor

5. Adaptadores

5.1 geostream_maps_maplibre (MUST, F9)

5.2 geostream_maps_google (SHOULD, F9)

6. Interpolación visual (FR-MAP-005, ampliada por ADR-0030)

Predicción visual (E3). La línea base no predice. La pista E añade dead reckoning opcional (22 §3.6): si el predictor del cliente está habilitado y hay ≥ 2 muestras recientes, puede extrapolar hasta predict_horizon_ms con velocidad decaída, y mezcla el resultado con la posición real durante predict_blend_ms. Condiciones obligatorias para extrapolar: velocidad ≥ predict_min_speed_mps y plausibilidad ≥ predict_min_plausibility.

La predicción es local y efímera. Una posición PREDICTED nunca se publica, nunca entra en la cola sin conexión, nunca se persiste y nunca se registra (08 §7.6). Un cliente que publique una muestra con kind = PREDICTED o INTERPOLATED la verá rechazada con INVALID_LOCATION en el campo quality.kind (09 §3.3): la línea de medición solo admite MEASURED. Esto no es una preferencia de diseño, es una defensa contra el caso en el que un cliente "optimizado" convierte una suposición en un hecho persistente.

7. Privacidad frente a proveedores de mapas

8. Provider Gateway (E5)

El servidor sí usa proveedores de mapas y rutas, pero no los posee: delega la consulta con la credencial del tenant (BYOK). ADR-0028.

8.1 Principios

8.2 Manifiesto y términos

Antes de usar un proveedor, PUT exige terms.checked_on (fecha en la que el tenant confirmó sus términos). Si algún cambio relevante del manifiesto ocurre después, la respuesta pasa a exigir una nueva confirmación. Además, el gateway aplica la ContentPolicy del proveedor: qué categorías de vía se pueden pedir, qué perfil de datos se devuelve y qué se puede almacenar en caché.

8.3 Límites y coste

8.4 Alternativa sin proveedor: grafo vial propio

GS_GRAPH_PATHS carga grafos .gsg descargados por región. Habilitan map-matching integrado y, como COULD, POST /v1/tools/route sobre el grafo propio (ADR-0032). Con esta alternativa, ninguna coordenada sale de la infraestructura del tenant, lo que resuelve de raíz el caso de uso que no puede aceptar el BYOK. La atribución ODbL de los datos OSM es obligatoria (13 §2); antes de distribuir bases derivadas hace falta revisión legal (10 §9).