Capa GeoJSON
Paquete de extensión para superponer GeoJSON directamente sobre un mapa. Dado que las características se rasterizan en mosaicos y se dibujan, no es necesario crear un objeto por característica, incluso con decenas de miles de elementos. Los nombres de API, los valores predeterminados de estilo y el comportamiento de las pruebas de impacto están unificados en Android, iOS y React.
01 · Resumen
GeoJSON se analiza y convierte en un modelo de características ligero, luego se dibuja a través de la canalización de mosaicos ráster de MapConductor. Independientemente del proveedor (Google Maps, MapLibre, MapKit, HERE, etc.), el código y la apariencia son idénticos.
Perspectiva
Admite FeatureCollection, entidades individuales, geometrías simples y secuencias de texto de RFC 8142. También hay un analizador de flujo.
Dibujar mosaicos
Rasteriza entidades en mosaicos de 512px y las coloca en el mapa como una capa ráster. No depende de la función vectorial del proveedor.
Prueba de impacto
Detecta clics con las mismas coordenadas que se usaron para dibujar. Admite polígonos con agujeros, geometría multiparte y colecciones de geometría.
Todos los tipos de geometría se admiten de manera idéntica en las tres plataformas.
02 · Uso básico
Simplemente coloque la capa en el ámbito content de la vista del mapa. El análisis se realiza en segundo plano y el resultado se pasa a features.
dependencies {
implementation("com.mapconductor:geojson:<version>")
}val mapViewState = rememberMapLibreMapViewState(
cameraPosition = MapCameraPosition(
position = GeoPoint.fromLongLat(139.7671, 35.6812),
zoom = 12.0,
),
)
val layerState = remember { GeoJSONLayerState() }
var features by remember { mutableStateOf(emptyList<GeoJSONFeature>()) }
LaunchedEffect(Unit) {
features = withContext(Dispatchers.IO) {
assets.open("wards.geojson").use(GeoJSONParser::parseStream)
}
}
MapLibreMapView(state = mapViewState) {
GeoJSONLayer(state = layerState, features = features)
}.package(url: "https://github.com/MapConductor/ios-geojson-layer", from: "<version>") // iOS 15+ / depends on MapConductorCore
@StateObject private var mapViewState = MapKitViewState(
cameraPosition: MapCameraPosition(
position: GeoPoint.fromLongLat(longitude: 139.7671, latitude: 35.6812),
zoom: 12.0
)
)
@StateObject private var layerState = GeoJSONLayerState()
@State private var features: [GeoJSONFeature] = []
var body: some View {
MapKitMapView(state: mapViewState) {
GeoJSONLayer(state: layerState, features: features)
}
.task {
features = GeoJSONParser.parse(fileURL: wardsURL)
}
}npm install @mapconductor/react-geojson \
@mapconductor/js-sdk-core @mapconductor/js-sdk-reactconst state = useMapLibreViewState({
mapDesignType: MapLibreDesign.OsmBrightJa,
cameraPosition: createMapCameraPosition({
position: createGeoPoint({ latitude: 35.6812, longitude: 139.7671 }),
zoom: 12,
}),
});
const layerState = useMemo(() => new GeoJSONLayerState(), []);
const features = useMemo(() => GeoJSONParser.parseFeatures(GEOJSON), []);
return (
<MapLibreMapView2D state={state}>
<GeoJSONLayer state={layerState} features={features} />
</MapLibreMapView2D>
);Cuando cambia el contenido o el estilo de una característica, la capa invalida la URL del mosaico interna. La caché ráster del SDK del mapa no seguirá devolviendo imágenes antiguas.
03 · Determinación del estilo
El estilo se resuelve en tres capas
Tres capas: 'valores predeterminados para toda la capa', 'anulaciones por característica' y 'determinación dinámica mediante StyleProvider'. Las capas inferiores tienen más prioridad y los elementos no especificados se heredan de las capas superiores. El procedimiento básico es comenzar solo con los valores predeterminados de la capa y agregar capas inferiores solo según sea necesario.
3-1. Propiedades de estilo
Solo se manejan cuatro propiedades. Además, en el lado de la capa hay opacity, visible y minZoom / maxZoom para controlar la visualización.
3-2. Establecer los valores predeterminados de capa
Primero comience aquí. El valor pasado a `GeoJSONLayerState` será la base para todas las entidades. Como el estado es observable, se volverá a dibujar si se asigna más tarde.
val layerState = remember {
GeoJSONLayerState(
strokeColor = Color.argb(220, 30, 136, 229),
fillColor = Color.argb(60, 30, 136, 229),
strokeWidth = 1.5f,
pointRadius = 8f,
opacity = 1f,
minZoom = 8, maxZoom = 22,
)
}
// El estado es observable: asigna después y los mosaicos se regeneran
layerState.fillColor = Color.argb(90, 214, 64, 69)@StateObject private var layerState = GeoJSONLayerState(
opacity: 1.0,
layerStyle: GeoJSONTileRenderer.LayerStyle(
strokeColor: UIColor(red: 30/255, green: 136/255, blue: 229/255, alpha: 0.86),
fillColor: UIColor(red: 30/255, green: 136/255, blue: 229/255, alpha: 0.24),
strokeWidth: 1.5,
pointRadius: 8.0
)
)
// Los campos de LayerStyle son let, así que reemplaza la estructura entera (alpha está en UIColor)
layerState.layerStyle = GeoJSONTileRenderer.LayerStyle(
strokeColor: layerState.layerStyle.strokeColor,
fillColor: UIColor.systemRed.withAlphaComponent(0.35),
strokeWidth: layerState.layerStyle.strokeWidth,
pointRadius: layerState.layerStyle.pointRadius
)import { colorArgb, colorRgb, argbToCss } from '@mapconductor/react-geojson';
const layerState = useMemo(() => new GeoJSONLayerState({
strokeColor: colorArgb(220, 30, 136, 229),
fillColor: colorArgb(60, 30, 136, 229),
strokeWidth: 1.5,
pointRadius: 8,
opacity: 1,
minZoom: 8, maxZoom: 22,
}), []);
// colorRgb para un color opaco, argbToCss al bajar a CSS
const legend = argbToCss(colorRgb(30, 136, 229)); // "rgba(30,136,229,1.0000)"Formatos de especificación de color
Android y React usan enteros de 32 bits ARGB (alfa es el byte más significativo), iOS usa `UIColor` con alfa en el color mismo. React tiene las funciones auxiliares `colorArgb(a,r,g,b)` / `colorRgb(r,g,b)` / `argbToCss()` en el mismo orden que `Color.argb()` de Android. Los colores predeterminados en las 3 plataformas están unificados en #1E88E5 (líneas opacas, relleno con alfa 128).
Anular para cada entidad
Una entidad puede tener por sí misma `strokeColor` / `fillColor` / `strokeWidth` / `pointRadius` / `visible`. Si permanece como `null`, se usa el valor predeterminado; si hay un valor, este prevalece. Si el estilo se determina en el momento de cargar los datos (y no cambia después), este método es el más directo y rápido.
val parsed = GeoJSONParser.parseStream(input)
// Tras el análisis, lee las propiedades y fija el estilo
val styled = parsed.map { f ->
when (f.properties["status"]) {
"alert" -> f.copy(fillColor = Color.argb(120, 214, 64, 69), strokeWidth = 3f)
"closed" -> f.copy(visible = false)
else -> f // Se deja en null, así que se usa el valor por defecto de la capa
}
}
GeoJSONLayer(state = layerState, features = styled)let parsed = GeoJSONParser.parse(data: data)
let styled = parsed.map { f -> GeoJSONFeature in
switch f.properties["status"] as? String {
case "alert":
var copy = f
copy.fillColor = UIColor.systemRed.withAlphaComponent(0.47)
copy.strokeWidth = 3.0
return copy
case "closed":
var copy = f
copy.visible = false
return copy
default:
return f // Si se deja en nil, vale el valor por defecto de la capa
}
}const styled = useMemo(() =>
GeoJSONParser.parseFeatures(GEOJSON).map(f => {
const status = f.properties.status;
if (status === 'alert') {
return { ...f, fillColor: colorArgb(120, 214, 64, 69), strokeWidth: 3 };
}
if (status === 'closed') return { ...f, visible: false };
return f; // Si se deja en null, vale el valor por defecto de la capa
}), []);
<GeoJSONLayer state={layerState} features={styled} />Determinar dinámicamente con StyleProvider
Los «estilos determinados por reglas», como colorear según los valores de `properties`, resaltar solo la entidad seleccionada o cambiar el umbral desde la IU, se escriben en `StyleProvider`. Se llama para cada entidad, recibe los valores predeterminados de la capa y devuelve el estilo final.
Lo que se pasa
La entidad en sí (incluidas las propiedades) y los valores predeterminados de la capa en ese momento. Lo habitual es copiar los valores predeterminados y cambiar solo una parte.
Lo que devuelve
Un LayerStyle con los 4 elementos completados. Para los elementos que no se tocan, se pueden devolver los valores predeterminados tal cual, para que se aplique la configuración de la primera capa.
// Es una fun interface, así que basta con un lambda
val densityStyle = GeoJSONStyleProviderInterface { feature, defaultStyle ->
val pop = (feature.properties["population"] as? Number)?.toInt() ?: 0
val fill = when {
pop > 500_000 -> Color.argb(150, 173, 20, 87)
pop > 200_000 -> Color.argb(120, 244, 143, 177)
else -> Color.argb(80, 248, 187, 208)
}
defaultStyle.copy(fillColor = fill) // Lo que no toques conserva su valor por defecto
}
val layerState = remember {
GeoJSONLayerState(styleProvider = densityStyle)
}
// Cambiarlo después vuelve a evaluar todos los features
layerState.styleProvider = DefaultGeoJSONStyleProviderfinal class DensityStyleProvider: GeoJSONStyleProvider {
func style(
for feature: GeoJSONFeature,
defaultStyle: GeoJSONTileRenderer.LayerStyle
) -> GeoJSONTileRenderer.LayerStyle {
let pop = (feature.properties["population"] as? Int) ?? 0
let fill: UIColor =
pop > 500_000 ? UIColor(red: 173/255, green: 20/255, blue: 87/255, alpha: 0.59)
: pop > 200_000 ? UIColor(red: 244/255, green: 143/255, blue: 177/255, alpha: 0.47)
: UIColor(red: 248/255, green: 187/255, blue: 208/255, alpha: 0.31)
return GeoJSONTileRenderer.LayerStyle(
strokeColor: defaultStyle.strokeColor,
fillColor: fill,
strokeWidth: defaultStyle.strokeWidth,
pointRadius: defaultStyle.pointRadius
)
}
}
layerState.styleProvider = DensityStyleProvider()// En la web, en lugar de un StyleProvider, mapea el resultado del análisis y
// dale a cada feature su propio estilo (el resultado es el mismo)
const styleOf = (props: Record<string, unknown>) => {
const pop = Number(props.population ?? 0);
if (pop > 500_000) return colorArgb(150, 173, 20, 87);
if (pop > 200_000) return colorArgb(120, 244, 143, 177);
return colorArgb(80, 248, 187, 208);
};
const features = useMemo(
() => GeoJSONParser.parseFeatures(GEOJSON)
.map(f => ({ ...f, fillColor: styleOf(f.properties) })),
[],
);Cuando se reemplaza `StyleProvider` o cambia el estado al que hace referencia, se reevalúan los estilos de todas las entidades y se recrean los mosaicos. Dado que se llama para cada entidad, por favor precalcule fuera del proveedor cualquier proceso pesado (expresiones regulares, red, análisis de fechas, etc.).
3-5. Cuál usar
04 · Detección de toques
Como `MapConductor` tiene un solo escucha de clics, la aplicación se encarga de reenviar a la capa. No lo registramos automáticamente de forma intencional. `processClick` devuelve `true` solo cuando golpea una entidad.
val layerState = remember {
GeoJSONLayerState(
onClick = { feature, position -> selected = feature },
)
}
MapLibreMapView(
state = mapViewState,
onMapClick = { point ->
// Se evalúa con una tolerancia de 15 px, que sigue al zoom
val consumed = layerState.processClick(point, 15.0, mapViewState.zoom)
if (!consumed) selected = null
},
) {
GeoJSONLayer(state = layerState, features = features)
}layerState.onClick = { feature, position in
selected = feature
}
MapKitMapView(
state: mapViewState,
onMapClick: { point in
selected = nil
layerState.processClick(geoPoint: point)
}
) {
GeoJSONLayer(state: layerState, features: features)
}const layerState = useMemo(() => new GeoJSONLayerState({
onClick: (feature, position) => setSelected(feature),
}), []);
// Reenvíalo desde el manejador de clic del mapa
const handleMapClick = (point: GeoPointInterface) => {
const consumed = layerState.processClick(point, 10, state.camera.zoom);
if (!consumed) setSelected(null);
};Tolerancia de píxeles
Si pasa una tolerancia en píxeles y el zoom actual a processClick, la detección seguirá el zoom. Si se omite, se usa la tolerancia predeterminada de coordenadas mundiales (aprox. 0,0002°).
Al superponerse
Se devuelve la entidad dibujada en último lugar (es decir, la de arriba).
Geometrías admitidas
Puntos, líneas, polígonos con agujeros, colecciones de geometría multiparte.
05 · Volumen de datos y carga
Para grandes datos, use un analizador de transmisión y analice en segundo plano. Hay dos formas de mantener las entidades.
Estático, para grandes volúmenes
Objetos de datos inmutables. Son ligeros porque no crean objetos de estado incluso con decenas de miles de elementos. Para GeoJSON grande, use esto.
Para pocos, que cambian con frecuencia
Se puede actualizar de forma reactiva uno por uno. Con muchos elementos, el costo de la gestión del estado se nota, así que úselo solo para lo necesario.
// parseStream para una FeatureCollection grande
val features = withContext(Dispatchers.IO) {
GeoJSONParser.parseStream(input)
}
// GeoJSON Text Sequences, RFC 8142
val seq = withContext(Dispatchers.IO) { GeoJSONSeqParser.parse(file) }
GeoJSONSeqParser.streamParse(file) { feature -> buffer.add(feature) }let features = GeoJSONParser.parse(fileURL: fileURL)
GeoJSONSeqParser.streamParse(fileURL: fileURL) { feature in
// Añade de uno en uno, por lotes o de forma persistente
}const features = GeoJSONParser.parseFeatures(text);
const seq = GeoJSONSeqParser.parse(text); // Secuencia delimitada por líneas
// tileSize es 512 por defecto; puedes subirlo a densidad Retina
<GeoJSONLayer state={layerState} features={features} tileSize={512} />val layerState = remember { GeoJSONLayerState() }
var features by remember { mutableStateOf(emptyList<GeoJSONFeature>()) }
var loading by remember { mutableStateOf(true) }
// parseStream returns immutable GeoJSONFeature values — no state object per feature
LaunchedEffect(Unit) {
features = withContext(Dispatchers.IO) {
context.assets.open("tokyo-buildings.geojson")
.use(GeoJSONParser::parseStream)
}
loading = false
}
MapLibreMapView(state = mapViewState) {
GeoJSONLayer(state = layerState, features = features)
}
if (loading) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
}