/** * Mapa de features. Dibuja la geometría GeoJSON del proyecto (puntos, líneas, * polígonos) sobre Google Maps y permite seleccionar una feature tocándola. * * Nota: las tiles de Google Maps requieren conexión; la geometría sí se dibuja * sin red. Requiere una API key de Google Maps (ver app.config.js / README). */ import * as Location from 'expo-location'; import React, { useMemo, useRef } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import MapView, { Marker, Polygon, Polyline, PROVIDER_GOOGLE } from 'react-native-maps'; import { Feature } from '../api/types'; import { COLORS } from './components'; import { geometryToShapes, LatLng, regionFor } from './geojson'; const STATUS_COLOR: Record = { pending: '#9aa0a6', in_progress: '#1f6f43', completed: '#2e7d32', blocked: '#b00020', }; function colorFor(status?: string): string { return (status && STATUS_COLOR[status]) || COLORS.primary; } export function FeatureMap({ features, selectedId, onSelect, }: { features: Feature[]; selectedId: number | null; onSelect: (id: number) => void; }) { const mapRef = useRef(null); const { shaped, region } = useMemo(() => { const all: LatLng[] = []; const shaped = features.map((f) => { const shapes = geometryToShapes(f.geometry); all.push(...shapes.points, ...shapes.lines.flat(), ...shapes.polygons.flat()); return { feature: f, shapes }; }); return { shaped, region: regionFor(all) }; }, [features]); const recenter = async () => { const perm = await Location.requestForegroundPermissionsAsync(); if (!perm.granted) return; const pos = await Location.getCurrentPositionAsync({}); mapRef.current?.animateToRegion({ latitude: pos.coords.latitude, longitude: pos.coords.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01, }); }; if (!region) { return ( Las features no tienen geometría. ); } return ( {shaped.map(({ feature, shapes }) => { const color = colorFor(feature.status); const selected = feature.id === selectedId; const stroke = selected ? '#000' : color; return ( {shapes.points.map((p, i) => ( onSelect(feature.id)} /> ))} {shapes.lines.map((line, i) => ( onSelect(feature.id)} /> ))} {shapes.polygons.map((poly, i) => ( onSelect(feature.id)} /> ))} ); })} void recenter()}> ); } const styles = StyleSheet.create({ container: { flex: 1 }, empty: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24 }, locBtn: { position: 'absolute', right: 16, bottom: 16, width: 48, height: 48, borderRadius: 24, backgroundColor: '#fff', justifyContent: 'center', alignItems: 'center', elevation: 4, shadowColor: '#000', shadowOpacity: 0.2, shadowRadius: 4, shadowOffset: { width: 0, height: 2 }, }, locIcon: { fontSize: 22, color: COLORS.primary }, });